中石油训练赛 - Watch Later(状压dp)

题目链接:点击查看

题目大意: 给出一个长度为 n 的字符串,字符串中共有 k 种不同的字符,现在问删除掉所有字符的最小操作数,对于每种字符需要确定一个先后顺序,每次需要删除掉当前所有的这种字符才能去删除下一种字符,而对于每一种字符的删除,一段当前字符组成的连续区间可以一起删除,现在问最小操作数,注意一下,当某段字符被删除后,剩下的字符会进行拼接

题目分析:训练时读错题了,以为是青蛙祖玛那样的删除,然后看 n 那么小,觉得像是区间dp,就没再纠结了(这个样例给的也太水了吧,开局两分钟以为是签到题,冲了一发,然后带歪榜了,阿这)

其实是一道十分水的状压dp,因为不同的字符最多有 20 种,且需要确定一种最优的先后顺序进行删除,所以我们设 dp[ i ] 为第 i 种状态下,二进制为 1 的字符已经被删除掉的最小花费,答案显然就是 dp[ ( 1 << k ) - 1 ] 了

简单讲一下如何转移,假设当前枚举的状态为 i ,对于已经被删除掉的字符就不用管了,对于没有被删除掉的字符,我们统计一下当前状态下,删除掉每种字符的贡献,然后择优转移就好了,统计每种字符的贡献实质上就是统计当前字符被分成了多少段不相邻的区间

代码:
 

#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<unordered_map>
using namespace std;
    
typedef long long LL;
    
typedef unsigned long long ull;
    
const int inf=0x3f3f3f3f;
  
const int N=1e6+100;

int dp[(1<<20)+100],id[150],cnt[25];

char s[410];
 
int main()
{
#ifndef ONLINE_JUDGE
//  freopen("data.in.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//	ios::sync_with_stdio(false);
	memset(id,-1,sizeof(id));
	memset(dp,inf,sizeof(dp));
	int n,k;
	scanf("%d%d",&n,&k); 
	scanf("%s",s+1);
	int tot=0;
	for(int i=1;i<=n;i++)
		if(id[s[i]]==-1)
			id[s[i]]=tot++;
	dp[0]=0;
	for(int i=0;i<1<<k;i++)
	{
		memset(cnt,0,sizeof(cnt));
		int pre=-1;
		for(int j=1;j<=n;j++)
		{
			if(((i>>id[s[j]])&1)==0&&pre!=id[s[j]])
			{
				pre=id[s[j]];
				cnt[pre]++;
			}
		}
		for(int j=0;j<k;j++)
			dp[i|(1<<j)]=min(dp[i|(1<<j)],dp[i]+cnt[j]);
	}
	printf("%d\n",dp[(1<<k)-1]);














   return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_45458915/article/details/108543535