1. 程式人生 > >hiho226 Ctrl-C Ctrl-V

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^{2}),十億大小的資料自然不可能以此為解法,不過我們可以以此解法得出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]*4^{k}(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);
	}
}