新春消消乐 题解

给大家提前拜个早年!

第一档

测试贪心强不强的一档分,如果贪心策略很好的话,能拿到这一档的全部分数。

附上zyz的贪心代码 \((40pts)\):

#include<bits/stdc++.h>
using namespace std;
string str;
map<string,int>Ans;
int calc(string s)
{
	// cout<<s<<'\n';
	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<<'\n';
}

不过这一档也可以通过暴力枚举删数次序,来算出答案。

第二档

将连续相同的数放在一个块里。

建一个结构体,存放连续相同的数的值和长度。

把字符串变成块后,只会出现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\n",ans);
    //system("pause");
    return 0;
}
posted @ 2021-02-07 11:12  江北南风  阅读(107)  评论(0编辑  收藏  举报