Crime HDU - 4623(状压DP,不同进制转换)

 

题目链接:HDU - 4623

题意:将1~n,n个数重新排列组合,使得每相邻的两个数互质;问总共有多少中方案;

思路:n最大是28,首先想到状压DP,2^28=268435456,肯定会爆栈;所以还需要优化一下;通过观察可以发现,质因子相同的数可以看做一类;也就是说,6, 12, 24可以放到一组中,用一位表示,那么,将28个数分组后就构成了每个位不同进制的数字,由计算的共state=1727999种状态;时间复杂度:O(state*28*28), 大约1354751216;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e6;
//Ꮧ对1~28分组,有相同质因子的为一组,又1, 17, 19, 23与任何数互质,所以将其分为一组,在同一组有相同的对外性;
int group[]={0, 0, 1, 2, 1, 3, 4, 5, 1, 2, 6, 7, 4, 8, 9, 10, 1, 0, 4, 0, 6, 11, 12, 0, 4, 3, 13, 2, 9};
//提取每一组的代表数;
int digit[]={1, 2, 3, 5, 6, 7, 10, 11, 13, 14, 15, 21, 22, 26};
//prime[i][j]表示i组的数与j组的是否互质;
bool prime[20][20];
int gcd(int a, int b){
    return b==0?a:gcd(b, a%b);
}
//初始化prime数组;
void init(){
    //这里为什么i,j都是0~13?可以保留疑问,继续向下看;
    for(int i=0; i<14; i++){
        for(int j=0; j<14; j++){
            prime[i][j]=(gcd(digit[i], digit[j])==1?1:0);
        }
    }
}
//bit[i]表示i位是bit[i]进制, num表示给出的范围总共包括的组数,也就表示一共有多少位;
int bit[20], dp[maxn][20], num, mod;
//计算cnt数组的状态下,表示的变进制数;cnt[i]表示第i为是cnt[i];
int get_state(int *cnt){
    int state=0;
    for(int i=0; i<=num; i++){
        state=state*bit[i]+cnt[i];
    }
    return state;
}
//计算在state状态下的cnt数组;
void get_cnt(int state, int *cnt){
    for(int i=num; i>=0; i--){
        cnt[i]=state%bit[i];
        state/=bit[i];
    }
}
//suf[i]表示在第i位加1,十进制数增加suf[i]。例如二进制100=4,1000=8;
int suf[20];
void get_suf(){
    suf[num]=1;
    for(int i=num-1; i>=0; i--){
        suf[i]=suf[i+1]*bit[i+1];
    }
}
int solve(int state){
    memset(dp, 0, sizeof(dp));
    get_suf();
    int cnt[20];
    memset(cnt, 0, sizeof(cnt));
    for(int i=0; i<=num; i++){
        cnt[i]=1;
        dp[get_state(cnt)][i]=bit[i]-1;///dp[state][j]表示在state状态下,排列是以j为最后一个数字
        cnt[i]=0;
    }
    for(int k=1; k<=state; k++){
        get_cnt(k, cnt);
        for(int i=0; i<=num; i++){
            if(cnt[i]==0) continue;//如果i位没有选数就构不成以i为结尾就跳过;这里表示在state状态下最后选的是i组的数,然后接下来枚举j作为新状态结尾,这个j和i互质;
            for(int j=0; j<=num; j++){
                if(!prime[i][j]||cnt[j]>=bit[j]-1) continue;//如果i,j组数不互质或者j组无数可选了,就不选j组数;细心的朋友会发现0组咋办?即如果之前选了1,此时还能选19,所以prime[0][0]是等于1的;
                int s=k+suf[j];
                dp[s][j]=(dp[s][j]+dp[k][i]*(bit[j]-cnt[j]-1)%mod)%mod;///新状态就是旧状态乘以j的剩余数量,剩余x个那肯定是结尾有x种搭配嘛
            }
        }
    }
    int ans=0;
    for(int i=0; i<=num; i++){
        ans=(ans+dp[state][i])%mod;
    }
    return ans%mod;
}
int main(){
    int T;
    scanf("%d", &T);
    init();
    while(T--){
        int n;
        scanf("%d%d", &n, &mod);
        num=0;
        for(int i=1; i<=n; i++){
            num=max(num, min(i, group[i]));///取出有多少种不同的集合,例如 0, 11,2,那么num=3;
        }
        int cnt[20];
        memset(cnt, 0, sizeof(cnt));
        for(int i=1; i<=n; i++){
            cnt[group[i]]++;
        }
        for(int i=0; i<=num; i++){
            bit[i]=cnt[i]+1;///因为我们有可不选这个状态,所以bits就应该cnt+1,
        }
        printf("%d\n", solve(get_state(cnt)));
    }
    return 0;
}

  

posted on   师姐的迷弟  阅读(130)  评论(0编辑  收藏  举报

导航

点击右上角即可分享
微信分享提示