目录
原问题:如果每次操作可以打一个字,复制或粘贴,那么一个人操作100次最多可以输出多少个字
问题来自知乎https://www.zhihu.com/question/408855446
我们一共有3种操作,C(复制),V(粘贴),A(打一个字),并假设1.一开始粘贴区是空的;2.复制是全选后复制
DP
dp[i][j]
j为0,1,2,分别代表A,C,V
dp[i][j]表示操作i次,最后一次操作是j,打的字的个数
显然dp[0][0]=dp[0][1]=dp[0][2]=0
dp[i][0]=max(dp[i-1][0],dp[i-1][1],dp[i-2][2])+1
dp[i][1]=max(dp[i-1][0],dp[i-1][1],dp[i-2][2])
dp[i][2]=
关于最后一个
假设当前是的量是T
CV=2T
CVV=3T
归纳可知,CVV...V(n-1个V)=nT
所以
dp[i-1][1]到dp[i][2]相当于从dp[i-1][1]复制了1次
dp[i-2][1]到dp[i][2]相当于从dp[i-2][1]复制了2次
以此类推
于是容易写出代码
记录路径也很简单
下面这个是100次的,时间复杂度:
还是比较耗时的
#include<iostream>
#include<string>
#include<windows.h>
using namespace std;
typedef long long int LL;
const int N=100;
//测速宏
#define MEASURE(func) do{ \
LARGE_INTEGER m_nFreq,m_nBeginTime,nEndTime;\
QueryPerformanceFrequency(&m_nFreq);\
QueryPerformanceCounter(&m_nBeginTime);\
func;\
QueryPerformanceCounter(&nEndTime);\
std::cout << (double)(nEndTime.QuadPart - m_nBeginTime.QuadPart) * 1000 / m_nFreq.QuadPart << "ms" << std::endl;\
}while(0);
//快速幂
LL quick_pow(LL a,LL b){
LL res=1;
while(b){
if(b&1)res*=a;
a=a*a;
b>>=1;
}
return res;
}
/**
* 输出答案,并输出方法
*/
void f(){
LL dp[N+1][3]={{0,0,0}}; //+1 C V
LL path[N+1][3]={{-1,-1,-1}};
for(int i=1;i<=N;++i){
for(int j=0;j<3;++j){
if(dp[i-1][j]>dp[i][0]){
dp[i][0]=dp[i-1][j];
dp[i][1]=dp[i-1][j];
path[i][0]=j;
path[i][1]=j;
}
}
++dp[i][0];
for(int j=0;j<i;++j){
LL t=dp[j][1]*(i-j+1);
if(t>dp[i][2]){
dp[i][2]=t;
path[i][2]=-j;
}
}
}
int t=0;
for(int j=1;j<3;++j){
if(dp[N][j]>dp[N][t])t=j;
}
string s;
int id=N,ans=t;
while(id>0){
if(path[id][t] < 0){
s= string(id+path[id][t], 'V') + s;
id=-path[id][t];
t=1;
}
else{
s= (t == 0 ? "A" : "C") + s;
--id;
t=path[id][t];
}
}
cout<<dp[N][ans]<<endl;//4*(3^32)
cout<<s<<endl;
// cout<<4*quick_pow(3,32)<<endl;
}
/**
* 返回答案
* @return 答案
*/
LL g(){
LL dp[N+1][3]={{0,0,0}}; //+1 C V
for(int i=1;i<=N;++i){
for(int j=0;j<3;++j){
if(dp[i-1][j]>dp[i][0]){
dp[i][0]=dp[i-1][j];
dp[i][1]=dp[i-1][j];
}
}
++dp[i][0];
for(int j=0;j<i;++j){
LL t=dp[j][1]*(i-j+1);
if(t>dp[i][2]){
dp[i][2]=t;
}
}
}
int ans=0;
for(int j=1;j<3;++j){
if(dp[N][ans]>dp[N][ans])ans=j;
}
return dp[N][ans];
}
int main(){
MEASURE(f());
MEASURE(g());
return 0;
}
贪心
假设当前字数是T,接着操作
CV=2T,耗费了2步,效率
CVV=3T,耗费了3步 ,效率
归纳可知,CVV...V(n-1个V)=nT,耗费了n步 ,效率
得到最大值点e
由于n是整数,所以考虑2,3
得知,CVV最划算,所以我们可以堆叠CVV
先找到n<=9时的最优解(堆叠CVV的用红色)
n=1 ('A', 1)
n=2 ('AA', 2)
n=3 ('AAA', 3)
n=4 ('AAAA', 4), ('AACV', 4)
n=5 ('AAACV', 6), ('AACVV', 6)
n=6 ('AAACVV', 9)
n=7 ('AAAACVV', 12), ('AAACVCV', 12), ('AAACVVV', 12), ('AACVCVV', 12), ('AACVVCV', 12)
n=8 ('AAACVCVV', 18), ('AAACVVCV', 18), ('AACVVCVV', 18)
n=9 ('AAACVVCVV', 27), ('AAAACVCVV', 24), ('AAAACVVCV', 24), ('AAACVCVCV', 24), ('AAACVCVVV', 24), ('AAACVVVCV', 24), ('AACVCVCVV', 24), ('AACVCVVCV', 24), ('AACVVCVCV', 24), ('AACVVCVVV', 24), ('AACVVVCVV', 24)
可以看到,其实堆叠CVV要到5才比较管用,所以,接下来针对n>=5
接下来的问题就是开头和结尾,开头应该用几个A,结尾应该怎么操作
开头:
开头堆叠的A不应该超过4个,因为只要超过4个,都不如AAC后一直接V划算(因为这时候的复制一次+2,A只+1)
结尾:
假设开头用了k个A,那么结尾还剩下(n-k)%3,设为r
如果r=0,没有什么特殊的操作
如果r=2,
第一种选择是CV,相当于*2
第二种选择是VV
如果复制过一次的,可以少CVV一次,进行观察,那CVVCV=*6,CVVVV=*5,所以应该选择CV
如果没有,那也至少有2个A,依然CV(只有1个A,r=2,说明n=3,与我们讨论范围n>4矛盾)
如果r=1
如果复制过一次,可以少CVV一次,进行观察,那CVVV=*4,CVCV=*4,所以直接V或者少CVV一次,然后CVCV
如果没有,那也只能A
综上所述
n>4时
枚举开头的A不应该超过4个,
中间堆叠CVV,
结尾观察余数r
r=0不操作
r=1,复制过则V或者少CVV一次,然后CVCV,没有则A
r=2,CV
时间复杂度O(1)
#include<iostream>
#include<string>
#include<windows.h>
using namespace std;
typedef long long int LL;
const int N=100;
//测速宏
#define MEASURE(func) do{ \
LARGE_INTEGER m_nFreq,m_nBeginTime,nEndTime;\
QueryPerformanceFrequency(&m_nFreq);\
QueryPerformanceCounter(&m_nBeginTime);\
func;\
QueryPerformanceCounter(&nEndTime);\
std::cout << (double)(nEndTime.QuadPart - m_nBeginTime.QuadPart) * 1000 / m_nFreq.QuadPart << "ms" << std::endl;\
}while(0);
//快速幂
LL quick_pow(LL a,LL b){
LL res=1;
while(b){
if(b&1)res*=a;
a=a*a;
b>>=1;
}
return res;
}
/**
* 输出答案,并输出方法
*/
void f(){
LL ans=0;
int pre,mid,tail;//前导A,中间CVV,结尾CV,
bool tail_A;//结尾A
int loop_time=std::min(4,N);
for(int i=1;i<=loop_time;++i){
int t=N-i,num=t/3,remain=t%3;
LL temp;
if(remain==0){
temp=i*quick_pow(3,num);
}
else if(remain==1){
if(num>0){
temp=i*quick_pow(3,num-1)*4;
}
else{
temp=i+1;
}
}
else{
temp=i*quick_pow(3,num)*2;
}
if(temp>ans){
ans=temp;
pre=i;
if(remain==0){
mid=num;
tail=0;
tail_A=false;
}
else if(remain==1){
if(num>0){
mid=num-1;
tail=2;
tail_A=false;
}
else{
mid=0;
tail=0;
tail_A=true;
}
}
else{
mid=num;
tail=1;
tail_A=false;
}
}
}
string s(pre,'A');
while(mid--)s.append("CVV");
while(tail--)s.append("CV");
if(tail_A)s+='A';
cout<<ans<<endl;
cout<<s<<endl;
}
/**
* 返回答案
* @return 答案
*/
LL g(){
LL ans=0;
int loop_time=std::min(4,N);
for(int i=1;i<=loop_time;++i){
int t=N-i,num=t/3,remain=t%3;
LL temp;
if(remain==0){
temp=i*quick_pow(3,num);
}
else if(remain==1){
if(num>0){
temp=i*quick_pow(3,num-1)*4;
}
else{
temp=i+1;
}
}
else{
temp=i*quick_pow(3,num)*2;
}
if(temp>ans){
ans=temp;
}
}
return ans;
}
int main(){
MEASURE(f());
MEASURE(g());
return 0;
}
最后给一个工具函数,用来演算
typedef long long int LL;
/**
* 根据操作,计算答案
* @param s 操作字符串
* @return 答案
*/
LL getAnsByString(const string& s){
LL ans=0,pre=0;
for(const char& c:s){
if(c=='A')++ans;
else if(c=='C')pre=ans;
else ans+=pre;
}
return ans;
}