dp小怪兽吃糖果

小怪兽吃糖果
Description

从前有一个小怪兽,他因为不认真做\text{acm}acm集训队的训练被\text{acm}acm的幕后老大施加了魔法进入了一个一维世界。

在这个世界中只有完成一个吃糖果的游戏才能摆脱困境,这个游戏可以可以看做如下规则进行:初始时有一个长为nn的糖果序列,这个序列由kk种不同的糖果构成,在游戏开始时你需要对这kk种糖果确定一个操作的先后顺序,之后便按该顺序进行吃糖果,只有吃完当前种的糖果才可以吃下一种糖果,每次操作是先选取一段由当前正在吃的糖果构成的区间,确定区间后把区间删除。

注意:如果当前某段糖果被吃掉后,剩余糖果会按原顺序进行拼接。

他只有成功花费最少的操作次数才视作完成该吃糖果的游戏。小怪兽试了好多好多次都没有成功,他利用了特殊技巧联系到了你,请你帮他拜托困境。

Input
第一行给出两个正整数n(1\le n\le 400),k(1\le k\le 20)n(1≤n≤400),k(1≤k≤20)分别表示糖果序列长度和糖果种类

第二行给出一个长度为nn的由小写字母构成的字符串表示糖果序列

Output
输出一个整数表示最小的操作次数

Sample Input 1

5 2
abaab
Sample Output 1

3
Hint

初始确定的序列为a,ba,b
第一次操作后:baabbaab
第二次操作后:bbbb
第三次操作后变为空串,所以操作次数为33
想说的都在代码里边。

//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
//#pragma GCC optimize(2)
#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<cmath>
#include<cctype>
#include<vector>
#include<set>
#include<queue>
#include<algorithm>
#include<sstream>
#include<ctime>
#include<cstdlib>
#include<random>
#include<deque>
#include<cassert>
#define X first
#define Y second
#define L (u<<1)
#define R (u<<1|1)
#define pb push_back
#define mk make_pair
#define Mid ((tr[u].l+tr[u].r)>>1)
#define Len(u) (tr[u].r-tr[u].l+1)
#define random(a,b) ((a)+rand()%((b)-(a)+1))
#define db puts("---")
using namespace std;
//void rd_cre() { freopen("d://dp//data.txt","w",stdout); srand(time(NULL)); }
//void rd_ac() { freopen("d://dp//data.txt","r",stdin); freopen("d://dp//AC.txt","w",stdout); }
//void rd_wa() { freopen("d://dp//data.txt","r",stdin); freopen("d://dp//WA.txt","w",stdout); }
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
const int N=5010,mod=1e9+7,INF=0x3f3f3f3f;
const LL inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-6;
int n,k;
int a[N];
int f[1<<21],mp[N];
int cnt[21];
char s[N];
int main()
{
//	ios::sync_with_stdio(false);
//	cin.tie(0);
	scanf("%d%d%s",&n,&k,s+1);//s+1就从1开始喽。 
	for(int i=1,tot=0;i<=n;i++) {
		if(mp[s[i]-'a']) 
		{
			a[i]=mp[s[i]-'a']-1;
			continue;
		}
		mp[s[i]-'a']=++tot;
		a[i]=tot-1;
		//mp[]是第tot个颜色是谁(这里字母都用数字来表示了)。
		//a[i]是第i位是哪一种颜色。 
	}
	memset(f,0x3f,sizeof(f));
	f[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>>a[j]&1) continue;//已经被涂抹了! 
			if(pre==-1) pre=a[j];
			else {
				if(a[j]!=pre) cnt[pre]++,pre=a[j];
			}//这么做直接就把每一种状态直接给求到最后,没有状态之间的转移了 
		}//cnt数组是花费在这个颜色上的个数是多少。 
		if(pre!=-1) cnt[pre]++;
		else break;
		// cout<<i<<endl;
		// for(int i=0;i<k;i++) cout<<cnt[i]<<' ';
		// puts("");
		for(int j=0;j<k;j++) if(!(i>>j&1)) 
		{  //这个颜色如果是0的话。 
			f[i|(1<<j)]=min(f[i|(1<<j)],f[i]+cnt[j]);	
		   //枚举这种状态(i)所有能够转移的状态! 
		}
	}
	printf("%d\n",f[(1<<k)-1]);
	return 0;
	//状压dp学长的这个代码美剧每一种状态,并把每一种状态的之后可能转移的状态的数量也求出来。
    //	 
}
/*

*/

以上学长的代码很多变量,要明白每一个代表的是什么!!
以下我自己的T掉的垃圾代码可看可不看。主要是告诫自己要灵活,多想

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<stack>
#include<map>
#include<cstring>
using namespace std ;
const int N =1<<21;
int wei,f[N],ans;
char a[600] ;
int t ,w;//动态规划,000101 由000001转移过来和由000100转移过来不一样。 0
//看上去是一种动态规划!
bool check(int i,int j)//w是2
{   
    int cnt=0 ;
    if(i==j)return 0;
    for(int s=0;s<w;s++)
    {    
         if(((i>>s)&1)==1&&((j>>s)&1)==0)return 0 ;
         if(((i>>s)&1)==0&&((j>>s)&1)==1)
        {
        wei=s;//标记着如果合法我将会涂抹掉第几个糖果
        cnt++;
        }
        if(cnt>1)return 0 ; 
    }
//    cout<<"hwdhjhwdjidjiwd"<<i<<" "<<j<<" "<<cnt<<endl;
//    if(cnt==1)
   	  return 1 ;
//    else return 0 ;
}
int main()  
{   int i ,j;  
//    memset(f,0x3f,sizeof f );
//    f[0]=0 ;
//      for(int i=0;i )
    map<int,char>ma;
    map<char,int >ta; 
    map<char,int>biao; 
    scanf("%d%d",&t,&w); 
    for(int i=1;i<((1<<t)+2);i++)f[i]=600;
    f[0]=0;  
//    char c;
//    c='a';
    int jilu=0;
	scanf("%s",a); 
    for(int i=0;i<strlen(a);i++) 、
    {
    	if(biao[a[i]]==0)
    	{
    		biao[a[i]]=1;
    		ma[jilu]=a[i];
    		jilu++;
    		if(jilu==w)break; 
		}
	}
//    for(i=0 ;i<w;i++)//i是;第几种糖果。
//    {  
//        ma[i]=c;
//        c++;
//    }
//    for(int i =0;i<w;i++)cout<<ma[i]<<" "<<endl;
    int zhi=1<<w;//i,j是二进制表示的糖果消除完成情况
    for(i=0;i<zhi;i++)//枚举所有的转移情况
    {                      
      for(j=0;j<zhi;j++)
      {
        if(!check(i,j))//没有check i,j 
       {
//	   cout<<"ikonw "<<i<<" "<<j<<endl; 
       continue ;      
       }
        //已经得到了两种合法的状态转移了。比如00001 00011
        //接下来就是求在当前的状态下转移到下一个状态所需要的步数
       ta.clear();//ta表示i这个序列(先前的序列)的已经被抹掉的糖果数量
       int tem=i,ming=0;
       while(tem)//拿到t【a】的过程
       {
           if(tem&1)
           {
               ta[ma[ming]]=1;
           }
        tem>>=1;
        ming++;
       }
       bool flag=0;
       ans= 0 ;//枚举每一种情况的时候我们都要进行将ans清空
       int step = 0 ;
//       cout<<"I:"<<i<<"J:"<<j<<"wie:"<<wei<<endl;
       while(step<t)
       { 
//	      cout<<"i:"<<i<<endl;
           if(ta[a[step]]==1)
        {
               step++;
              continue ;//之前已经被消除了,过
        } 
        else if((a[step]==ma[wei])&&(flag==0))
        {  
//            cout<<"wei"<<wei<<"i:"<<i<<"j"<<j<<endl;        
             ans++;
             flag=1;
             step++;
             continue ;
        }else if((a[step]==ma[wei])&&(flag==1))
        {
             step++;
             continue;
        }
        else
        {
        flag= 0;
        step++;
        continue ;
        }
       }
       f[j]=min(f[j],ans+f[i]);
//        cout<<"j"<<j<<f[j]<<"ans"<<ans<<endl;
	  }
//      f[j]=min(f[j],ans+f[i]);     
    }
//    for(int x=0;x<zhi;x++)
//    {
//        cout<<f[x]<<endl;
//    }
//    cout<<f[zhi-1]<<endl;
        printf("%d\n",f[zhi-1]); 
//f[j]是二进制j的所需要的最小步数
    return 0 ;
}
//我的思路是枚举每一种情况和下一种情况。先判断是否合法再每一个都判断,多了不少重复操作。
//比如check函数和在循环里边的只盯着那个要变化的位置了,其实可以一锅端掉
//然后再枚举之后的状态,我想起来以前的状压国王的那道题目,那道题目不得不那样因为每行都有具体变化
//灵活点。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值