接竹竿
Description
一天,神犇和 LCR 在玩扑克牌。他们玩的是一种叫做“接竹竿”的游戏。
游戏规则是:一共有 n 张牌,每张牌上有一个花色 c 和一个点数 v,花色不超过 k 种。将这些牌依次放入一列牌的末端。若放入之前这列牌中已有与这张牌花色相同的牌,你可以选择将这张牌和任意一张花色相同的牌之间的所有牌全部取出队列(包括这两张牌本身),并得到与取出的所有牌点数和相同的分数。现在已知 LCR 把这 n 张牌放入队列的顺序,求她最多能得多少分。
输入顺序即为 LCR 放入队列的顺序。即,ci表示第 i 张放入的牌的花色,vi表示第 i 张放入的牌的点数。
请注意,如果你知道类似的纸牌游戏,请尤其仔细地阅读规则,以免因为理解题意错误而出现不必要的问题。
Analysis
这题不是丁钩钓鱼!!!
题意可以简化为:请你找出若干个不相交的首尾相同子序列,使其元素之和最大。
那么很显然是一道线性动规题,设dp[i]为前i张牌的最大得分值。对于第i张牌可以使用也可以无视,无视即为dp[i-1],使用就是dp[j]+sum(j+1,i),j为与i花色相同的牌。写出第一个动规方程:
dp[i]=max(dp[i-1],dp[j-1]+sum(j,i))
这是接近O(n^2)的算法,如果用可变长数组优化一下可能能多得一点分,zyj应该就是这样写的。
不妨把所有颜色相同的列出来,dp[j1-1]+sum(j1,i),dp[j2-1]+sum(j2,i),dp[j3-1]+sum(j3,i)...,如果同时减去sum(1,i),得到dp[j1-1]-sum(1,j1),dp[j2-1]-sum(1,j2),dp[j3-1]-sum(1,j3),很显然,无论i为多少,大小顺序都不会改变。记录一下每种颜色的最大值,直接O(1)转移,再进行更新,就没了。
动规方程:
dp[i]=max(dp[i-1],best[c[i]]+sum)
Code
#include <bits/stdc++.h>
typedef long long int64;
const int N=1000010;
int n,k,c[N];
int64 v[N],sum,best[N],dp[N];
int read_int(){
char ch=getchar();
while(ch<'0'||ch>'9')
ch=getchar();
int i=0;
while(ch>='0'&&ch<='9'){
i=(i<<1)+(i<<3)+ch-'0';
ch=getchar();
}
return i;
}
int64 read_int64(){
char ch=getchar();
while(ch<'0'||ch>'9')
ch=getchar();
int64 i=0;
while(ch>='0'&&ch<='9'){
i=(i<<1)+(i<<3)+ch-'0';
ch=getchar();
}
return i;
}
int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d",&n,&k);
memset(best,0x80,sizeof(best));
for(int i=1;i<=n;i++)
c[i]=read_int();
for(int i=1;i<=n;i++)
v[i]=read_int64();
for(int i=1;i<=n;i++){
sum+=v[i];
dp[i]=dp[i-1];
if(best[c[i]]>best[0]&&best[c[i]]+sum>dp[i])dp[i]=best[c[i]]+sum;
best[c[i]]=std::max(best[c[i]],dp[i-1]-sum+v[i]);
}
printf("%lld\n",dp[n]);
return 0;
}