【cofun1623】寿司晚宴
Sample Input
输入样例1
3 10000
输入样例2
4 10000
输入样例3
100 100000000
Sample Output
样例输出1
9
样例输出2
21
样例输出3
3107203
Hint
分析:
题意:把一个数的集合分为两个集合,记为G,M,
显然,G中数的质因子与M中数的质因子应互不相同,因此选了一个数等同于选择了它的质因子。
虽然2<=n<=500,数据比较大,但是经过计算,我们会发现每个数最多只能有一个>= n√ 的质因子,其余质因子都< n√ ;而< n√ 的质数只有8个,加上对题目理解,可以考虑状压DP。- 把每个数中< 500−−−√ 的质因子压进二进制数中,并把它>= 500−−−√ 的质因子记录下来。
- 按照最大质因子的大小给寿司排序,方便DP。
- 状压DP:
令f[i][j]表示集合G中质因子状态为i,集合M中质因子状态为j的方案数;dp[0][i][j]表示新出现的大质数归入集合G,原先G状态为i,M状态为j的方案数;dp[1][i][j]表示新出现的大质数归入集合M,原先G状态为i,M状态为j的方案数;
当枚举到新的大质数时,分情况,即不归入(不DP),归入G和归入M,可平行状压DP;
当含该新大质数的状态枚举完后,应将dp[0][i][j]和dp[1][i][j]归并为f[i][j],注意归并时因dp[0][i][j]和dp[1][i][j]都包含了不归入新的大质数的状态,应再减去一次,因类似背包,直接-f[i][j]即可。
注意:运用背包思想降了一维是表示当前枚举到第几个大质数,所以dp转移时要倒序。
具体转移方程在代码中注解。【因为比较多判断条件,会影响美观XDDD
【做这题的时候看了dalao们的题解,感觉不是很能理解,还是自己打舒服~
- 代码:
#include <bits/stdc++.h>
using namespace std;
const int su[8] = {2, 3, 5, 7, 11, 13, 17, 19};
struct info{
int da, su;
}e[505];
long long n, m, p, i, j, k, ans, dp[2][1 << 8][1 << 8], f[1 << 8][1 << 8];
inline int read()
{
long long x = 0, w = 1;
char ch = 0;
while(ch < '0' || ch > '9')
{
if (ch == '-')
w = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
x = x * 10 + ch - '0', ch = getchar();
return x * w;
} //读入优化
inline void write(long long x)
{
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}//输出优化
bool cmp(info a, info b)
{
return a.da < b.da || (a.da == b.da && a.su < b.su);
}
int main()
{
n = read(), p = read();
//读入
for(i = 2; i <= n; i ++)
{
m ++;
for(j = 0, k = i; j < 8; j ++)
if (k % su[j] == 0)
{
for(; k % su[j] == 0; k /= su[j]);
e[m].su |= (1 << j);
}
e[m].da = k;
}
sort(e + 1, e + m + 1, cmp);
//把美味度的质因子压进二进制并按大质数排序1.&2.
f[0][0] = 1;
for(i = 1; i <= m; i ++)
{
if (e[i].da == 1 || e[i].da != e[i - 1].da)
{
memcpy(dp[0], f, sizeof(f));
memcpy(dp[1], f, sizeof(f));
}
//新出现的大质数不归入集合G与M
for(j = (1 << 8) - 1; j >= 0; j --)
for(k = (1 << 8) - 1; k >= 0; k --)
{
if (! (e[i].su & k))
dp[0][j | e[i].su][k] = (dp[0][j | e[i].su][k] + dp[0][j][k]) % p;
//新出现的大质数归入集合G
if (! (e[i].su & j))
dp[1][j][k | e[i].su] = (dp[1][j][k | e[i].su] + dp[1][j][k]) % p;
//新出现的大质数归入集合M
}
//新出现的大质数归入集合G或M,同时将小质数的状态进行状压DP
if (e[i].da == 1 || e[i].da != e[i + 1].da)
for(j = 0; j < 1 << 8; j ++)
for(k = 0; k < 1 << 8; k ++)
f[j][k] = ((dp[0][j][k] - f[j][k]) % p + dp[1][j][k] + p) % p;
//将两种平行的情况归并
}
//DP3.
for(j = 0; j < 1 << 8; j ++)
for(k = 0; k < 1 << 8; k ++)
if (! (j & k))
ans = (ans + f[j][k]) % p;
//统计答案
write(ans);
//输出
return 0;
}
下午好咩咩咩鹿啦啦~
已经晚上19:23了,上来抒发一下内心的咩咩皮(MMP),我在下一篇【队伍统计】中写跟同学讲了蛮久的寿司晚宴,然后,刚刚才帮他把这题啪对。。很多小细节都会导致致命的错误,他居然在转移方程里多了个+号,看了贼久ODX所以做题时一定要仔细啊!!!!!