接竹竿
题目描述
一天,神犇和 LCR 在玩扑克牌。他们玩的是一种叫做“接竹竿”的游戏。
游戏规则是:一共有 张牌,每张牌上有一个花色 和一个点数 ,花色不超过 种。将这些牌依次放入一列牌的末端。若放入之前这列牌中已有与这张牌花色相同的牌,你可以选择将这张牌和任意一张花色相同的牌之间的所有牌全部取出队列(包括这两张牌本身),并得到与取出的所有牌点数和相同的分数。现在已知 LCR 把这 张牌放入队列的顺序,求她最多能得多少分。
输入顺序即为 LCR 放入队列的顺序。即, 表示第 张放入的牌的花色, 表示第 张放入的牌的点数。
请注意,如果你知道类似的纸牌游戏,请尤其仔细地阅读规则,以免因为理解题意错误而出现不必要的问题。
输入格式
第一行两个整数 。
第二行, 个整数 表示花色,满足 。
第三行, 个整数 表示点数。
输出格式
输出一行一个整数,表示最多能得到的分数。
样例
样例输入 1
7 3
1 2 1 2 3 2 3
1 2 1 2 3 2 3
样例输出 1
13
样例解释 1
第 1 步,向队列加入 1。现在的队列:1
第 2 步,向队列加入 2。现在的队列:1,2
第 3 步,向队列加入 1。现在的队列:1,2,1
第 4 步,向队列加入 2,取出 2,1,2。现在的队列:1
第 5 步,向队列加入 3。现在的队列:1,3
第 6 步,向队列加入 2。现在的队列:1,3,2
第 7 步,向队列加入 3,取出 3,2,3。现在的队列:1
这是今天机房考试的第一道题(T3),以为是一道水题,结果胡写了一个转移方程
因为我们要求出最大值,所以记录一下上一个相同花色出现的位置,与dp[i-1]取一个MAX,然后在判断上一个同种花色的有没有用过,判断是否有重复
大概是下面这样的:
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #include<cstdlib> #include<string> #include<cmath> using namespace std; inline long long rd(){ long long x=0,f=1; char ch=getchar(); for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1; for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; return x*f; } inline void write(long long x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return ; } long long n,m; long long a[1000006],s[1000006]; long long set[1000006],la[1000006]; long long sum[1000006]; long long dp[1000006]; int main(){ n=rd(); m=rd(); for(long long i=1;i<=n;i++) s[i]=rd(); for(long long i=1;i<=n;i++){ a[i]=rd(); la[i]=set[s[i]]; set[s[i]]=i; sum[i]=sum[i-1]+a[i]; } memset(set,0,sizeof(set)); for(long long i=0;i<=n;i++){ if(la[i]==0){ dp[i]=dp[i-1]; continue; } if(set[la[i]]){ if(dp[la[i]]+sum[i]-sum[la[i]]>dp[i-1]){ dp[i]=dp[la[i]]+sum[i]-sum[la[i]]; set[i]=1; } else dp[i]=dp[i-1]; } else{ if(dp[la[i]]+sum[i]-sum[la[i]-1]>dp[i-1]){ dp[i]=dp[la[i]]+sum[i]-sum[la[i]-1]; set[i]=set[la[i]]=1; } else dp[i]=dp[i-1]; } } printf("%lld",dp[n]); return 0; }
原本以为切了,结果被卡成了33分(一共33个点),后来发现写成了:
printf("%d",dp[n]);//我就是个SB
然后改了之后自信满满的在Loj上交了一发,结果只有87分,剩下的点大多数都比结果小一,最后还是没有调出来,有大佬可以在评论里告知
下面说一下我从dalao哪里学来的正解
首先看一下数据范围,是10^6的,用N^2肯定过不了,但是我们可以很轻松的想出N^2算法
我们定义dp[i]为取到第i张纸牌时能取到的最大值
则状态转移为max(dp[i-1],之前的同种花色转移过来的)
中间我们记录一个前缀和sum,就可以很快完成
下面给出核心代码:
for(int i=1;i<=n;i++){ dp[i]=dp[i-1]; for(int j=1;j<=i-1;j++){ dp[i]=max(dp[i],sum[i]+dp[j-1]-sum[j-1]); } }
很明显,这是会T的(凑字)
但是我们细细一想,计算上一个同种颜色的值是可以在DP过程中维护的
我们用一个f[i]数组来记录颜色为i的之前的可以转移的最大值,而计算过程则是sum[i]+dp[j-1]-sum[j-1]
除了sum[i],另外两个值都可以维护,而下标i则可以和dp[i]一起枚举
f[i]=max(f[i],dp[i-1]-sum[i-1])
(如果想问下标为什么为i-1的,去上面再看一下N^2算法)
然后我们就把DP优化成了o(n)的
然而还是有几个注意事项的,因为f的值必定为非正数,所以预先赋值为 -INF
下面给出代码:
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #include<cstdlib> #include<string> #include<cmath> using namespace std; inline long long rd(){ long long x=0,f=1; char ch=getchar(); for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1; for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; return x*f; } inline void write(long long x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return ; } long long inf=1LL<<60; long long n,m; long long a[1000006],s[1000006]; long long sum[1000006]; long long f[1000006]; long long dp[1000006]; int main(){ n=rd(); m=rd(); for(long long i=1;i<=m;i++) f[i]=-inf;//赋值为负无穷 for(long long i=1;i<=n;i++) s[i]=rd(); for(long long i=1;i<=n;i++){ a[i]=rd(); sum[i]=sum[i-1]+a[i];//维护前缀和 } for(long long i=1;i<=n;i++){ dp[i]=max(dp[i-1],sum[i]+f[s[i]]); f[s[i]]=max(f[s[i]],dp[i-1]-sum[i-1]);//维护每种颜色的最大值 } printf("%lld",dp[n]);//输出要写lld(QAQ) return 0; }