CF #235 D. Roman and Numbers状压DP

一、题意

题目传送门:Problem - 401D - Codeforces

拙劣翻译:问给出的数位的全排列组成的数当中,模m等于0的数的个数,但是不能有前导0。

看到这个数据范围,我是直摇头qaq,和队友阅读理解了很久其他ac代码,下面谈谈合理的思路。

二、状态压缩

一点预备:(来源于状态压缩_*Slime*的博客-CSDN博客_状态压缩

 而关于状态压缩,想着把一个状态表示成二进制形态。这个题,n的每一位都能表示成0或1(取或是不取)。有一个看了很久才明白的点:我们标记的取数字只是用于标记这个位置的数被拿走了,取出来的数字每一次都加在我们排列的数字个位,这样也方便计算余数。

比如对于n=140,010表示我们取第二位的数字4,现在的排列就是4;从010转移到110就是再取第一位的数字1,排列就是41;

三、注意的问题

我们用dp[i][j]来表示当前数字的状态为i,余数为j的方案数,最后的答案就为dp[(1<<n)-1][0]。

转移方程dp[i][(k*10+num[j])%m]+=dp[i^(1LL<<(j-1))][k]

状态i可以从i^(1LL<<(j-1))转移过来,比如101可以从001转移过来,转移后的余数=(k*10+num[j])%m。

前导零问题我们在选择第一位时保证不为0就行。

重复情况,比如n=221,那么100转移到110和010转移到110是重复的,我们记录每一个数字(数值)的使用情况,保证不重复。

#include<bits/stdc++.h>
using namespace std;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
ll dp[(1<<18)+1][101];
int a[20],num[20],vis[20];
int cnt=0;
int main()
{
	ll n,m;
	cin>>n>>m;
	while(n)
	{
		a[++cnt]=n%10;
		n/=10;
	}
	for(int i=cnt,j=1;i>=1;i--,j++)
		num[j]=a[i];//cout<<num[j]<<endl;
	//初始化
	dp[0][0]=1;
	for(ll i=0;i<(1<<cnt);i++) //遍历每一种状态 
	{
		memset(vis,0,sizeof(vis));
		for(ll j=1;j<=cnt;j++)//遍历每一位数字 
		{
			//排除前导0 
			if(i==(1LL<<(j-1)) && !num[j])
				break;//i==(1ll<<(j-1))表示目前只选了一个j位数字(现在在个位,以后就成前导0了)
			
			//j位的数字已经被拿走了(与数字本身是什么无关,只是说位置) 
			if(!( i & (1LL<<(j-1)))|| vis[num[j]])
				continue;	
			//为了避免重复状态的产生 
				
			//用了这一个数字(本身),标记防止重复
			vis[num[j]]=1;
			
			for(ll k=0;k<m;k++)
			{
				dp[i][(k*10+num[j])%m]+=dp[i^(1LL<<(j-1))][k];
			 } 
		}
		
	}
	printf("%lld",dp[(1<<cnt)-1][0]);
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序和三三总有一个能跑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值