HDU 4623 Crime (状压DP + 数学优化)

题目:传送门

题意:问存在多少 1 ~ n 的排列满足任意相邻的两个数互质,输出答案取余 mod。

      1 <= n <= 28, 1 <= mod <= 30000

思路:很容易想到状压DP, dp[ i ][ j ]其中 i 是最后一个数要填的数,j 是当前使用过的数的状态,每一个二进制位对应一个数。也就是还没填 i 时使用过的数的状态为 j 的满足条件的方案数。

      然后发现 2^28 开不了数组,没法做,得想办法优化一下。

   我们对那些拥有相同质因子的数分为同一类,例如:2和4和8是同一类,3和9是同一类,但是,3 和 6 不是同一类。然后再将 1 17 19 23 分为同一类,因为它们和谁都互质。分完之后,总共最多有 15 组,那就可以开数组了,对每个数,用不同的进制表示,详见代码。

#include <bits/stdc++.h>
#define LL long long
#define mem(i, j) memset(i, j, sizeof(i))
#define rep(i, j, k) for(int i = j; i <= k; i++)
#define dep(i, j, k) for(int i = k; i >= j; i--)
#define pb push_back
#define make make_pair
#define INF INT_MAX
#define inf LLONG_MAX
#define PI acos(-1)
using namespace std;

const int N = 2e6 + 5;

short dp[19][N];
int c[30], vis[N], statu[30], bs[30], G[30][30];
int n, mod, up;
int cnt;
int tmp[10] = {2, 3, 5, 7, 11, 13, 17, 19, 23};

int get(int i, int x) {
    int res = (x % bs[i + 1]) / (bs[i]);
    return res;
}

void dfs(int s, int x) {
    if(s == 0) {
        dp[x][0] = 1; return ;
    }
    if(dp[x][s] != -1) return ;
    dp[x][s] = 0;
    rep(i, 1, cnt) {
        if(G[x][i] && get(i, s) >= 1) {
            dfs(s - bs[i], i);
            dp[x][s] = ((int)dp[x][s] + dp[i][s - bs[i]]) % mod;
        }
    }
}

void solve() {

    scanf("%d %d", &n, &mod);
    mem(dp, -1); mem(vis, 0);  mem(statu, 0); mem(c, 0);
    mem(G, 0);
    int ans = 0; cnt = 0;
    statu[++cnt] = 0;
    c[cnt] = 1;

    rep(i, 2, n) { /// 分类
        int st = 0;
        if(i == 17 || i == 19 || i == 23) {
            c[1]++; continue;
        }
        rep(j, 0, 5) {
            if(i % tmp[j] == 0) {
                st |= (1 << j);
            }
        }
        if(!vis[st]) {
            statu[++cnt] = st;
            c[cnt] = 1;
            vis[st] = cnt;
        }
        else c[vis[st]]++;
    }

    rep(i, 1, cnt) rep(j, 1, cnt) { /// 判断是否互质
        if((statu[i] & statu[j]) == 0) G[i][j] = 1;
    }

    up = 0; bs[1] = 1;
    rep(i, 1, cnt) { /// 每一位都有各自的进制
        bs[i + 1] = bs[i] * (c[i] + 1);
        up += bs[i] * c[i];
    }

    rep(i, 1, cnt) {
        dfs(up - bs[i], i);
        ans = ((int)ans + dp[i][up - bs[i]]) % mod;
    }

    rep(i, 1, cnt) {
        while(c[i] > 1) { /// 同一类的还有先取后取之分,要乘 c[i]的阶乘
            ans = ((int)ans * c[i]) % mod;
            c[i]--;
        }
    }

    printf("%d\n", ans);

}

int main() {

    int _; scanf("%d", &_);
    while(_--) solve();

    return 0;

}

原文地址:https://www.cnblogs.com/Willems/p/12400595.html

时间: 2024-10-14 23:48:51

HDU 4623 Crime (状压DP + 数学优化)的相关文章

【bzoj1097】[POI2007]旅游景点atr 状压dp+堆优化Dijkstra

题目描述 FGD想从成都去上海旅游.在旅途中他希望经过一些城市并在那里欣赏风景,品尝风味小吃或者做其他的有趣的事情.经过这些城市的顺序不是完全随意的,比如说FGD不希望在刚吃过一顿大餐之后立刻去下一个城市登山,而是希望去另外什么地方喝下午茶.幸运的是,FGD的旅程不是既定的,他可以在某些旅行方案之间进行选择.由于FGD非常讨厌乘车的颠簸,他希望在满足他的要求的情况下,旅行的距离尽量短,这样他就有足够的精力来欣赏风景或者是泡MM了^_^.整个城市交通网络包含N个城市以及城市与城市之间的双向道路M条

HDU 4284Travel(状压DP)

HDU 4284    Travel 有N个城市,M条边和H个这个人(PP)必须要去的城市,在每个城市里他都必须要“打工”,打工需要花费Di,可以挣到Ci,每条边有一个花费,现在求PP可不可以从起点1走完所有的他必须要去的城市,打完所有的工,并且成功回到起点1 由于H<=15,所以显然可以状压,预处理这些都必须去的城市之间的最短距离(可以floyd),然后状压DP[S][i]表示他到达了S这些城市后正处在第i个城市里(所以S & (1<<i) != 0)的剩余的最大的钱的数量,然

POJ 1185 炮兵阵地 状压DP+离散化优化

一开始能想到的状态就只有位压两行和当前行的行号,这样无论是空间和时间都是无法接受的. 但是因为炮兵的攻击范围比较大,而且又有地形限制,每一行的状态其实不多了,打表看了一下不超过80种,离散化一下就可以随意DP了. 据说题目也可以抽象成二分图最大匹配来搞?感觉复杂度有点高 #include <cstdio> #include <cstring> #include <iostream> #include <map> #include <set> #i

HDU 3001 Travelling 状压DP

链接:http://acm.hdu.edu.cn/showproblem.php?pid=3001 题意:还是环游地图的问题,只不过这回旅行者对自己有着严格的要求,地图上每个点的经过次数不能超过两次. 思路:依然是状压DP问题,根上一道很像,只不过这次对于每个点来说有三种状态,分别是未经过,经过一次,经过两次.所以要用三进制的数来进行状态压缩,这个关键点想明白了其他的和上一道基本一样了.对于我来说需要注意的是:能够到达某一个点经过了两次的状态的前一个状态是这个点已经经过了一次的状态,而不是从来未

NOJ 1116 哈罗哈的大披萨 【淡蓝】 [状压dp+各种优化]

我只能说,珍爱生命,远离卡常数的题...感谢陈老师和蔡神,没有他们,,,我调一个星期都弄不出来,,,, 哈罗哈的大披萨 [淡蓝] 时间限制(普通/Java) : 1000 MS/ 3000 MS          运行内存限制 : 65536 KByte总提交 : 73            测试通过 : 9 描述 热风哈罗哈(三牌楼)店正在搞活动:他们将提供一个大披萨给第一个告诉他们以下信息的人:一次购买任一种披萨,哪种单位面积的价格最低.问题初步想一想,好像是这么解决:“对于每个披萨计算平均

HDU 4336 容斥原理 || 状压DP

状压DP :F(S)=Sum*F(S)+p(x1)*F(S^(1<<x1))+p(x2)*F(S^(1<<x2))...+1; F(S)表示取状态为S的牌的期望次数,Sum表示什么都不取得概率,p(x1)表示的是取x1的概率,最后要加一因为有又多拿了一次.整理一下就可以了. 1 #include <cstdio> 2 const int Maxn=23; 3 double F[1<<Maxn],p[Maxn]; 4 int n; 5 int main() 6

HDU 3681 BFS&amp;状压DP&amp;二分

N*M矩阵,从F点出发,走完所有的Y点,每走一格花费1点电量,走到G点时,电量充满,D不可到达,问起始时的最小满电量可以走完所有Y,Y和G一共最多15个 先BFS出所有的F,Y,G之间的最短距离. 然后二分起始电量,对每个电量,做状压DP判断是否可行 #include "stdio.h" #include "string.h" #include "queue" using namespace std; int inf=0x3f3f3f3f; in

LianLianKan HDU - 4272(状压dp)

题意:就是连连看,有两个相同的就能消除,再加上两个特别的规定,一是只能从栈顶开始消除,而是两个相同的元素之间距离不能超过6,询问能否消除序列中所有元素. 思路:数据水,贪心就能过,但严谨的考虑,贪心显然不能解决所有问题.这题虽然序列很长,但是状态并不复杂,可以使用滚动的状压dp,然后考虑使用多少位表示状态,每一种状态表示第i位为栈首的序列状态,该位前最多能消除掉该位后四位,所以我们只需要表示后9位的状态即可,总计10位. 某位上1表示该位已被消除,0表示未被消除. dp[i][j]表示从i位开始

方格取数(1) HDU - 1565 (状压dp)

给你一个n*n的格子的棋盘,每个格子里面有一个非负数.     从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大. Input包括多个测试实例,每个测试实例包括一个整数n 和n*n个非负数(n<=20) Output对于每个测试实例,输出可能取得的最大的和 Sample Input 3 75 15 21 75 15 28 34 70 5 Sample Output 188 思路:状压dp,建立dp[i][j]二维数组,表示第i行状