zoukankan      html  css  js  c++  java
  • [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
    ", 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
    ", Ans);
    	}
    	return 0;
    }
    

      

  • 相关阅读:
    P1144 最短路计数
    P2966 [USACO09DEC]牛收费路径Cow Toll Paths
    P2419 [USACO08JAN]牛大赛Cow Contest
    P1462 通往奥格瑞玛的道路
    P1346 电车
    P1339 [USACO09OCT]热浪Heat Wave
    P1418 选点问题
    P1330 封锁阳光大学
    P1182 数列分段Section II
    P2661 信息传递
  • 原文地址:https://www.cnblogs.com/JoeFan/p/4160897.html
Copyright © 2011-2022 走看看