P2167 [SDOI2009]Bill的挑战------状压dp+枚举

题目链接:
https://www.luogu.com.cn/problem/P2167

在这里插入图片描述
分析:这道题用的状压dp思路确实不好想,也有人用容斥原理(数学的方法)做出来的不过我是没看懂,太弱了,dp的思路就是我们从第一位开始枚举每一个字母,然后把不匹配的字符串从字符串集状态中删除,枚举完最后一位为止最后得到答案,复杂度50× 2 15 2^{15} 215×50×26,算了一下大约 2 29 2^{29} 229左右,很卡时间,设dp[i][s]表示枚举到第i为合法的字符集状态s有多少种

AC代码:

#include<bits/stdc++.h>
#define RG register
using namespace std;
typedef long long ll;
const int mod = 1000003;
char s[20][50];
ll dp[20][1<<17+1];
int n,k;
/*二进制op有多少个1*/
int get_num(int op)
{
    
    
	int ans = 0;
	while(op)
	{
    
    
		ans++;
		op&=(op-1);
	}
	return ans;
}
void solve()
{
    
    
	memset(dp,0,sizeof(dp));
	cin>>n>>k;
	dp[0][(1<<n)-1] = 1;
	for(int i=1;i<=n;i++)
	{
    
    
		scanf("%s",s[i]);
	}
	int len = strlen(s[1]);
	/*从第一位开始枚举*/
	for(int i=0;i<len;i++)
	{
    
    
		/*枚举字符串集状态*/
		for(int op = (1<<n)-1;op>=0;op--)
		{
    
    
			if(dp[i][op])
			{
    
    
			if(get_num(op)<k) continue;
			/*每一位的枚举a~z字母*/
			for(char str = 'a';str<='z';str++)
			{
    
    
			 int tp = op;
			 /*把不合法的字符串从字符串集状态里面去掉*/	
			 for(int j=1;j<=n;j++)
			 {
    
    
			 	if(s[j][i]!='?'&&s[j][i]!=str) if(tp&(1<<(j-1))) tp-=(1<<(j-1));   
			 }
			 dp[i+1][tp]+=dp[i][op];
			 dp[i+1][tp]%=mod;
		    }
		    }
		}
	}
	ll ans = 0;
	for(int i=1;i<=(1<<n)-1;i++)
	{
    
    
		if(get_num(i)==k)
		{
    
    
			ans+=dp[len][i];
			ans%=mod;
		}
	}
	cout<<ans<<'\n';
}
int main()
{
    
    
	int t;
	cin>>t;
	while(t--)
	solve();
}

Guess you like

Origin blog.csdn.net/TheWayForDream/article/details/116426280