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

本文介绍了解决BZOJ1072问题的两种方法:一种是利用next_permutation函数进行暴力求解;另一种是采用状态压缩动态规划的方法。后者通过定义状态f[i][j]来避免重复计算,提高了效率。

题目链接: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;
}

  

 

转载于:https://www.cnblogs.com/JoeFan/p/4160897.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值