[BZOJ 1072] [SCOI2007] 排列perm 【状压DP】

题目链接:BZOJ 1072

这道题使用 C++ STL 的 next_permutation() 函数直接暴力就可以AC 。(使用 Set 判断是否重复)

代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <set>

using namespace std;

int T, d, l, Ans;
int A[15];

char Str[15];

typedef long long LL;

LL Num;

set<LL> S;

int main()
{
	scanf("%d", &T);
	for (int Case = 1; Case <= T; Case++) {
		scanf("%s%d", Str, &d);
		Ans = 0;
		S.clear();
		l = strlen(Str);
		for (int i = 0; i < l; i++) A[i] = Str[i] - ‘0‘;
		sort(A, A + l);
		while (true) {
			Num = 0;
			for (int j = 0; j < l; j++) Num = Num * 10 + A[j];
			if (S.count(Num) == 0 && Num % d == 0) {
				++Ans;
				S.insert(Num);
			}
			if (!next_permutation(A, A + l)) break;
		}
		printf("%d\n", Ans);
	}
	return 0;
}

  

但是比较慢,正常一点的解法应该是使用状压 DP 。

设定一个状态 f[i][j] ,其中 i 的二进制表示当前已经使用了原数串中的某些位 (在 i 中为 1 的位) ,j 表示当前的数字 mod d 的值。f[i][j] 表示达到这个状态的方案数。

那么状态转移就是 : f[i | (1<<k)][(j * 10 + A[k]) % d] += f[i][j]  ((i & (1<<k)) == 0)

由于原排列中的数字可能有重复的,所以我们计算了很多重复的方案数。

如果某个数字 i 在排列中出现了 Cnt[i] 次,那么最后的答案 Ans 应该 Ans /= (Cnt[i])! (排列数)。

代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

int T, d, l, Ans;
int A[15], Cnt[15], f[1024 + 5][1000 + 5];

char Str[15];

int main()
{
	scanf("%d", &T);
	for (int Case = 1; Case <= T; Case++) {
		scanf("%s%d", Str, &d);
		l = strlen(Str);
		memset(Cnt, 0, sizeof(Cnt));
		for (int i = 0; i < l; i++) {
			A[i] = Str[i] - ‘0‘;
			++Cnt[A[i]];
		}
		memset(f, 0, sizeof(f));
		f[0][0] = 1;
		for (int i = 0; i < (1 << l); i++) {
			for (int j = 0; j < d; j++) {
				for (int k = 0; k < l; k++) {
					if ((i & (1 << k)) == 0)
						f[i | (1 << k)][(j * 10 + A[k]) % d] += f[i][j];
				}
			}
		}
		Ans = f[(1 << l) - 1][0];
		for (int i = 0; i <= 9; i++) {
			for (int j = 1; j <= Cnt[i]; j++) {
				Ans /= j;
			}
		}
		printf("%d\n", Ans);
	}
	return 0;
}

  

时间: 2024-10-18 21:56:12

[BZOJ 1072] [SCOI2007] 排列perm 【状压DP】的相关文章

BZOJ 1072 SCOI2007 排列perm 状压DP

题目大意:给定n个数字,求这些数字的全排列中有多少数能被d整除 令f[i][j]为状态为i,余数为j的方案数 枚举最高位转移 小心爆int #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int n,d,ans,f[1<<10][1<<10],digit[1<<10],tens[10

[BZOJ1072][SCOI2007]排列perm 状压dp

1072: [SCOI2007]排列perm Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 2488  Solved: 1546[Submit][Status][Discuss] Description 给一个数字串s和正整数d, 统计s有多少种不同的排列能被d整除(可以有前导0).例如123434有90种排列能被2整除,其中末位为2的有30种,末位为4的有60种. Input 输入第一行是一个整数T,表示测试数据的个数,以下每行一组s和d,中间

B1072 [SCOI2007]排列perm 状压dp

很简单的状压dp,但是有一个事,就是...我数组开大了一点,然后每次memset就会T,然后开小就好了!!!震惊!以后小心点这个问题. 题干: Description 给一个数字串s和正整数d, 统计s有多少种不同的排列能被d整除(可以有前导0).例如123434有90种排列能 被2整除,其中末位为2的有30种,末位为4的有60种. Input 输入第一行是一个整数T,表示测试数据的个数,以下每行一组s和d,中间用空格隔开.s保证只包含数字0, 1 , 2, 3, 4, 5, 6, 7, 8,

BZOJ 1072 [SCOI2007]排列perm

考虑到s的长度特别小,只有10,可以考虑状压dp. 设F[S][d]表示当选了集合S(用二进制压位表示)中的所有位置,对D取模的结果为d的方案总数:不难想到转移和初始化. 初始化:F[0][0]=1  0在这里表示空集合 转移:F[S][(d * 10 + s[i]-'0') % D]=sum{F[S0][d]}  S0是S的一个子集,并且刚好只比S少一个元素i 注意,重复的数字被算了多遍.样例当中就有.因此最后的答案要除以所有重复的数字个数的阶乘. 看代码就明白啦~ (再补充一个要用到状压技巧

【以前的空间】bzoj 1072 [SCOI2007]排列perm

又颓废了一个下午,最近撸mc撸到丧失意识了,玩的有点恶心,于是找水题做,瞧不起颓废的自己啊. another水题. 这题题意很明显啦,就是找数字排列后组成的数去mod d=0后有多少种. 普通的搜索的话,是会tle的(应该是o(n!)没错?).注意到长度n还是比较小的,于是想到状压dp. 状态就是每个数取和不取组成的结果(就是00110表示第3,4个数取了啦,学过状压都知道). 然后转移就是f[i,j,k]表示现在取到第i个数状态为i余数为j有多少种情况, 那么f[i,j,(k*10+a[i])

BZOJ 1072: [SCOI2007]排列perm [DP 状压 排列组合]

题意:给一个数字串s和正整数d, 统计s有多少种不同的排列能被d整除(可以有前导0) 100%的数据满足:s的长度不超过10, 1<=d<=1000, 1<=T<=15 看到整除应该往余数方面想 $f[s][i]$表示当前已经选择的数的集合为$s$,余数为$i$的方案数 枚举下一个数字,用更新的写法转移 注意是有重复元素的排列!除上个阶乘 #include <iostream> #include <cstdio> #include <cstring&g

【BZOJ】1072: [SCOI2007]排列perm(状压dp+特殊的技巧)

http://www.lydsy.com/JudgeOnline/problem.php?id=1072 首先无限膜拜题解orz表示只会暴力orz 数据那么小我竟然想不到状压! orz 这种题可以取模设状态orz f[i,j]表示状态为i,mod d为j的方案 则答案为f[all, 0] 转移就太简单了orz f[i|1<<k, (j*10+c[k])%d]+=f[i, j] 然后我有一sb错一直没找到,wa了n发... QAQ #include <cstdio> #include

bzoj 1556: 墓地秘密【状压dp+spfa】

显然是状压,显然不可能把所有格子压起来 仔细观察发现只有机关周围的四个格子有用以及起点,所以我们用spfa处理出这些格子两两之间的距离(注意细节--这里写挂了好几次),然后设f[s][i]为碰完的机关石状态为s,现在在有用格子的第i个的最小停下次数,转移按照套路即可 #include<iostream> #include<cstdio> #include<queue> #include<cstring> using namespace std; const

BZOJ 4145: [AMPPZ2014]The Prices( 状压dp + 01背包 )

我自己只能想出O( n*3^m )的做法....肯定会T O( nm*2^m )做法: dp( x, s ) 表示考虑了前 x 个商店, 已买的东西的集合为s. 考虑转移 : 先假设我们到第x个商店去, so初始时 dp( x, s) = dp( x-1, s ) + d[x] 然后我们可以对第x个商店做01背包, dp(x, s + {h} ) = min( dp( x, s + {h} ) , dp( x, s) + c[x][h]) ) ( h ∉ s ). 之后我们再比较到第x个商店划不