HDU 5657 CA Loves Math 状压DP + 枚举

题意:

给出(A(2 leq A leq 11), n(0 leq n leq 10^9), k(1 leq k leq 10^9))
求区间([1, A^n])中各个数字互不相同的(A)进制数而且是(k)的倍数的个数。

分析:

如果(n > A),根据抽屉原理,(n)(A)进制数一定会有重复的数字。
所以下面只讨论(n leq a)的情况。

对于(k)的大小,分别使用不同的算法:

  • (k)比较小的时候,用状压DP:(d(S, x))表示当前数字集合为(S)且模(k)(x)的数字的个数。
    在数字的末位添加一个未在集合(S)出现的数字(i),则转移到状态(d(S',(x cdot A + i) % k))

  • (k)比较大的时候,直接暴力统计。枚举区间内所有(k)的倍数,判断一下如果没有重复数字答案+1。

第一种算法的复杂度为(O(2^A cdot k cdot A)),状态数乘转移数。
第二种算法复杂度为(O(A^n/k cdot A)),枚举次数乘判断时间。

找到一个合适的边界使得时限都能承受两种算法,测试了一下(25000)(30000),运行时间都是(3s)多,可以AC。

PS:(n=0)(n=1)这些边界情况最好特判一下。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long LL;

int A, n, k;
const int maxk = 30000;
const int maxa = 11;

LL d[1 << maxa][maxk];

int bitcount(int x) {
	int ans = 0;
	while(x) { if(x & 1) ans++; x >>= 1; }
	return ans;
}

bool check(LL x) {
	int S = 0;
	while(x) {
		int t = x % A;
		x /= A;
		if(S & (1 << t)) return false;
		S |= (1 << t);
	}
	return true;
}

int main()
{
	int T; scanf("%d", &T);
	while(T--) {
		scanf("%d%d%d", &A, &n, &k);

		if(n == 0) {
			if(k == 1) printf("1
");
			else printf("0
");
			continue;
		}
		if(n == 1) {
			printf("%d
", A / k);
			continue;
		}

		//DP
		if(k < maxk) {
			memset(d, 0, sizeof(d));
			for(int i = 1; i < A; i++) d[1 << i][i % k] = 1;
			for(int S = 0; S < (1 << A); S++) if(bitcount(S) < n) {
				for(int x = 0; x < k; x++) if(d[S][x]) {
					for(int i = 0; i < A; i++) if(!(S&(1<<i))) {
						d[S|(1<<i)][(x*A+i)%k] += d[S][x];
					}
				}
			}
			LL ans = 0;
			for(int S = 0; S < (1 << A); S++)
				if(d[S][0]) ans += d[S][0];
			printf("%lld
", ans);
		} else {
	    	if(n > A) n = A;
	    	LL An = 1; //A^n
    		for(int i = 1; i <= n; i++) An *= A;
			LL ans = 0;
			for(LL t = k; t <= An; t += k)
				if(check(t)) ans++;
			printf("%lld
", ans);
		}
	}

	return 0;
}
原文地址:https://www.cnblogs.com/AOQNRMGYXLMV/p/5357413.html