如果每次操作可以打一个字,复制或粘贴,那么一个人操作n次最多可以输出多少个字

目录

DP

贪心


原问题:如果每次操作可以打一个字,复制或粘贴,那么一个人操作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]= max \sum_{j=0}^{i-1} dp[j][1]*\left(i-j+1 \right )

关于最后一个

假设当前是的量是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次的,时间复杂度:1+2+\dots + n +2n = O\left(n^{2} \right )

还是比较耗时的

#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步,效率\sqrt 2

CVV=3T,耗费了3步 ,效率\sqrt[3]{3}

归纳可知,CVV...V(n-1个V)=nT,耗费了n步 ,效率\sqrt[n]{n}

\sqrt[n]{n} = e^{\frac{\ln n}{n}}

得到最大值点e

由于n是整数,所以考虑2,3

\sqrt 2 < \sqrt[3]{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;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nightmare004

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值