给大家提前拜个早年!
第一档
测试贪心强不强的一档分,如果贪心策略很好的话,能拿到这一档的全部分数。
附上zyz的贪心代码 ((40pts)):
#include<bits/stdc++.h>
using namespace std;
string str;
map<string,int>Ans;
int calc(string s)
{
// cout<<s<<'
';
if(Ans[s])return Ans[s];
int tot[10],maxx=9,temp=0,last=-1;
memset(tot,0,sizeof tot);
for(int i=0;i<s.size();i++)
tot[s[i]-'0']++;
for(int i=0;i<10;i++)
if(tot[i]>=tot[maxx])maxx=i;
int nowmax;
for(int num=0;num<10;num++){
// if(tot[num]!=tot[maxx])continue;
if(tot[num]==0)continue;
nowmax=tot[num]*tot[num];
last=-1;
for(int i=0;i<s.size();i++)
if(s[i]-'0'==num){
if(last+1<i-1)nowmax+=calc(s.substr(last+1,i-last-1));
if(last+1==i-1)nowmax++;
last=i;
}
if(last+1<s.size()-1)nowmax+=calc(s.substr(last+1,s.size()-last-1));
if(last+1==s.size()-1)nowmax++;
temp=max(temp,nowmax);
}
Ans[s]=temp;
return temp;
}
int main()
{
int T,ans=0;
cin>>T;
while(T --> 0){
cin>>str;
ans+=calc(str);
}
cout<<ans<<'
';
}
不过这一档也可以通过暴力枚举删数次序,来算出答案。
第二档
将连续相同的数放在一个块里。
建一个结构体,存放连续相同的数的值和长度。
把字符串变成块后,只会出现01交替的情况,从两边开始删一定不优,从中间开始删,块两边一定能合并。
因此,每删掉一个块,这个块两边的块会合成一个,相当于一次去掉了两个块,而性质满足块的数量 (le20) ,这样就可以愉快的搜索了。
第三档
留给写正解数组开小或者常数极大的选手。
满分题解
思考方法
看到题目后,单纯贪心策略有问题,考虑DP。
发现数据范围很小,要求出的是删除一段数字的最大值,可以用区间DP处理。
设置状态
设 (f_{l,r}) 表示 删除区间 ([l,r]) 的最大收益?
删除 ([l,r]) 可能会在边界还会有连续的数字,删除区间后还会有左右两边的合并问题,存在后效性。
本着写不出来多加一维的原则
设 (f_{l,r,siz}) 表示 删除区间 ([l,r]) 且区间后连着长度为 (siz) 与右端点相同数字的串,的最大收益。
目标状态为: (f_{1,len,0}) 。
状态转移
(f_{l,r,siz}) 的状态转移有两种状况
-
删去最右端的连续块。
-
在区间内找到代表的数字与最右端相同的一个块,将这两块之间删去,使得区间最右端连着的那部分变长。
枚举转移有点麻烦,可以通过记忆化搜索来解决。
递归边界为 (l=r)
Code
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 235
#define LL long long
using namespace std;
char S[N];
int idx,last_num,ans;
int T,len,f[N][N][110];//f如题解所述
struct block
{
int len,num;//len存块的长度,num存块所代表的的数字
}blo[N];
inline int qr()//平平无奇的快读
{
int x=0,w=1;char ch=0;
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*w;
}
int dfs(int l,int r,int siz)//记忆化搜索求解
{
if(f[l][r][siz])//搜过了就不搜了,直接返回答案
return f[l][r][siz];
if(l==r)//递归边界
return f[l][r][siz]=(blo[r].len+siz)*(blo[r].len+siz);//贡献为块本身长度加上右端连着的长度
f[l][r][siz]=dfs(l,r-1,0)+(blo[r].len+siz)*(blo[r].len+siz);//直接把右端和右端连着的一起删掉
for(register int k=l;k<r;k++)
if(blo[k].num==blo[r].num)//找到与右端数字相同的块
f[l][r][siz]=max(f[l][r][siz],dfs(l,k,blo[r].len+siz)+dfs(k+1,r-1,0));//收益为这一块与右端拼起来的收益 加上 删除这一块与右端之间的块的收益
return f[l][r][siz];//返回答案
}
int main()
{
//freopen("game.in","r",stdin);
//freopen("game.out","w",stdout);
T=qr();
while(T--)
{
scanf("%s",S+1);
len=strlen(S+1);
memset(f,0,sizeof(f));
last_num=-1;//最后一个块代表的数字
idx=0;//块的数量
for(register int i=1;i<=len;i++)//分成块
if(((S[i]^48))!=last_num)//如果这个数字与上一个块不相等
{
blo[++idx]=(block){1,(S[i]^48)};//新加一个块长度为1
last_num=(S[i]^48);//更新
}
else
blo[idx].len++;//如果数字相等块长+1
ans+=dfs(1,idx,0);//累加答案
}
printf("%d
",ans);
//system("pause");
return 0;
}