hiho226 Ctrl-C Ctrl-V
時間限制:10000ms
單點時限:1000ms
記憶體限制:256MB
描述
Yor are bored. So you open a notepad and keep pressing letter 'A' to type a lot of 'A's into the text area.
Suddenly an idea come out. If you can do the following 4 kinds of operations N times, how many 'A's you can type into the text area?
- A: Type a letter A into the text area
- Ctrl-A: Select all the letters in the text area
- Ctrl-C: Copy the selected letters into clipboard
- Ctrl-V: Paste the letters from clipboard into the text area
Assume N=7 you can get 9 'A's by doing following operations: A, A, A, Ctrl-A, Ctrl-C, Ctrl-V, Ctrl-V.
輸入
An integer N. (1 <= N <= 1000000000)
輸出
The maximum number of 'A's you can type into the text area. Note the answer may be very large so output the answer modulo 1000000007.
樣例輸入
7
樣例輸出
9
題目分析:
1.題是什麼?
題就是給你一個十億以內的數字n,代表你可以進行剛好n次以下操作,
A操作:向當前字串末尾新增一個字元'A'
Ctrl-A操作:選定當前整個字串
Ctrl-C操作:把當前選定的字串複製到剪貼簿
Ctrl-V操作:把剪貼簿中的字串貼上到當前字串末尾
你要輸出的就是通過n次操作你能作出的最長字串有多長,結果取模1000000007
2.思路
十億的資料大小,別說暴力,n複雜度也不合適,故而猜測不同大小的n的答案之間必然有規律,或者說一定有一種簡單的最優操作可以重複之然後達到最優答案,否則以低於n複雜度完成是不可能的.
故而我們可以簡單的思考一下最優解規律,首先
(1).Ctrl-A+Ctrl-C的順序是不可顛倒的,
(2).Ctrl-A+Ctrl-C+Ctrl-V中間插入A操作無論插在哪兒必然不如A+Ctrl-A+Ctrl-C+Ctrl-V,可以自己分析一下,
(3).Ctrl-A+Ctrl-V+Ctrl-C也必定不如Ctrl-A+Ctrl-C+Ctrl-V,因為Ctrl-V能貼出來的必定越來越長,大於等於之前的Ctrl-V效果,
(4).經過簡單的窮舉可以知道當n<7時無論如何操作最優解為n,當n>=時最優解必存在Ctrl-A+Ctrl-C+Ctrl-V操作
故而我們知道最優解中Ctrl-A+Ctrl-C+Ctrl-V必然是一個整體,Ctrl-A,Ctrl-C單獨出現也是無意義的.n>=7時A操作一定在最前,因為在做了Ctrl-A+Ctrl-C之後每一個Ctrl-V操作肯定優於A操作,因為剪貼簿裡的長度肯定大於等於1.
我自己的思路最開始是錯了的,在個人總結中分析錯誤點,這裡我給出官方的正確思路,其實關於答案是可以dp得到的,因為對於最優操作最後一個操作必然是A或者Ctrl-V,因為Ctrl-A,Ctrl-C並不實際增加長度,不可能放在最後一個,我們甚至可以這樣子,前6個窮舉直接賦值,因為當n>=7時最後一個操作必然是Ctrl-V,原因上面也總結了,故而可以得到dp的遞推公式(n>=7):
操作是Ctrl-V:假設最優解最後有k個連續Ctrl-V,則dp[i]=dp[i-2-k]*(k+1);
(dp[i-2-k]其實就是此時剪下板中字串的長度,這裡i之所以減去k+2是減去了k個連續的Ctrl-V以及前面連著的Ctrl-A+Ctrl-C,不減去前面連著的Ctrl-A+Ctrl-C你得到的可能不是剪下板中字串的長度!,乘以k+1是k個貼上來的加上原本的1個)
初始狀態的最優解窮舉就可以得到,即n<7時最優解為n,之後每一步遞推因為要窮舉k的可能性,故而dp複雜度為O(),十億大小的資料自然不可能以此為解法,不過我們可以以此解法得出n為1到100的答案進而研究答案規律,答案如下:
void solve(){
int n;
scanf("%d",&n);
//小於7時直接賦值
for(int i=0;i<7;i++) dp[i]=i;
for(int i=7;i<100;i++){
dp[i]=0;
//窮舉K
for(int k=i-3;k>0;k--) dp[i]=max(dp[i],dp[i-2-k]*(k+1)%mod);
}
for(int i=1;i<100;i++){
printf("%d %lld\n",i,dp[i]);
}
}
觀察答案可得規律: n>=16時,dp[i] = dp[i-5]*4;換句話說,當n足夠大的時候,最優的策略就是不斷的用Ctrl-A+Ctrl-C+Ctrl-V+Ctrl-V+Ctrl-V這麼5個操作把長度變成4倍.dp[i] = dp[x]*(11<=x<= 15且i=x+5k)
3.ac程式碼
#include <stdio.h>
typedef long long ll;
const ll mod=1000000007;
ll max(ll a,ll b){ return a>b?a:b; }
ll fastpower(ll a,ll b)
{
ll res=1;
ll temp=a;
while(b){
if(b&1) res=res*temp%mod;
temp=temp*temp%mod;
b>>=1;
}
return res;
}
void solve(){
int n;
scanf("%d",&n);
ll dp[16];
//小於7時直接賦值
for(int i=0;i<7;i++) dp[i]=i;
for(int i=7;i<16;i++){
dp[i]=0;
//窮舉K
for(int k=i-3;k>0;k--) dp[i]=max(dp[i],dp[i-2-k]*(k+1)%mod);
}
if(n<16) printf("%lld",dp[n]);
else{
int x=(n-11)%5+11;
printf("%lld",dp[x]*fastpower(4,(n-x)/5)%mod);
}
}
int main(){
solve();
return 0;
}
4.個人總結
我在看到這道題資料大小是十億時第一時間就否定了dp思路,儘管這道題有很多dp的特徵,最優解,線性變化,選擇操作,初始解固定等等,可我僅僅因為資料大小不能執行dp就否認了dp思路這是錯誤的,即時最終答案不能直接dp出來,也許可以借用dp找到答案的規律,
在否認dp思路之後我根據對最優解特徵的分析得出了一種錯誤結論:最優解一定是前面全是A操作,中間全是Ctrl-A+Ctrl-C+Ctrl-V,結尾全部是Ctrl-V這樣子的結構,其實這是錯誤的,前面全A是正確的,結尾全是Ctrl-V也是正確的,可是中間部分並不一定是連續的Ctrl-A+Ctrl-C+Ctrl-V,有一個反例:
當n為11時,A+A+A+A+A+Ctrl-A+Ctrl-C+Ctrl-V+Ctrl-V+Ctrl-V+Ctrl-V是我的演算法得到的答案,為25
可是實際上存在更優解A+A+A+Ctrl-A+Ctrl-C+Ctrl-V+Ctrl-V+Ctrl-A+Ctrl-C+Ctrl-V+Ctrl-V,為27,
我固執的認為獨立於Ctrl-A+Ctrl-C+Ctrl-V的單個的Ctrl-V都在末尾是錯誤的,雖然將這單個Ctrl-V後置後出來的一定是更多的,可是選擇在某個Ctrl-A+Ctrl-C前面Ctrl-V會使後面的所有Ctrl-V收益更大,而相比較哪個大就不確定了,我正是忽略了這個,導致了以下錯誤解法
5.我的錯誤解法
void solve(){
int n;
scanf("%d",&n);
if(n<7) printf("%d",n);
else{
ll ans=0;
for(int i=1;i<20;i++){
for(int j=0;j<20;j++){
if(i+j<n&&(n-i-j)%3==0){
ll temp=i;
temp=i*kuaisumi(2,(n-i-j)/3)%mod;
temp=(temp+j*(temp/2))%mod;
if(temp>ans) ans=temp;
}
}
}
printf("%lld",ans);
}
}