Bzoj4197: [Noi2015]寿司晚宴

原题网址:http://www.lydsy.com/JudgeOnline/problem.php?id=4197
这和今年THUSC的一题有点像,都是将根号以内的质数作状态。
首先每个质数都只能在一边出现一次,考虑状压,把包含质数的二进制压成状态,两边质数不能有交集,每次一个数放进来,枚举放左边,放右边和不放。但 n 以内质数有95个,考虑一个数大于n的因数不超过一个,所以可以说是这个数的特征,我们按这个特征分组,一个组里的数只能全被分到一边或两边都没有,那么这个质数就不要放到状态里了,直接看成类似分组背包就行了。用 f[S][S] 表示左边人状态是 S ,右边人状态是S的方案数, g[0][S][S] g[1][S][S] 分别表示当前这一组只能左边人(右边人)取的方案数,每次一组结束要合并一下答案,然后在给下一组开始分配一下答案,合并答案的时候要注意因为这一组两个人都没取在 g[0] g[1] 里都被算了,所以要减去一份,减去对应的 f <script type="math/tex" id="MathJax-Element-1697">f</script>即可。还有没有大因数的就没有限制,每个单独都能看成一组。
已经加了枚举子集的小小优化,但跑下来时间还是没达到bzoj平均水平,不过是1A,开心。(再也不会忘记+P再取模啦)

#include<bits/stdc++.h>
typedef long long ll;
const int N = 505;
const int all = 1 << 8;
const int p[8] = {2,3,5,7,11,13,17,19};
struct rec{
    int s,b;
}a[N];
int n,P;
ll f[all][all],g[2][all][all],ans;
bool cmp(const rec &a, const rec &b){
    return a.b > b.b;
}
int main(){
    scanf("%d%d",&n,&P);
    for (int i=1; i<n; i++){
        int temp = i + 1;
        for (int j=0; j<8; j++){
            a[i].s = a[i].s * 2 + (temp % p[j] == 0);
            while (temp % p[j] == 0) temp /= p[j];  
        }
        a[i].b = temp;
    }
    n--;
    std::sort(a+1,a+n+1,cmp);
    g[0][0][0] = 1;
    g[1][0][0] = 1;
    f[0][0] = 1;
    for (int i=1; i<=n; i++){
        if (i == 1 || a[i].b != a[i-1].b || a[i].b == 1)
            for (int S=0; S<all; S++)
                for (int _S=0; _S<all; _S++)
                    f[S][_S] = (g[0][S][_S] + g[1][S][_S] - f[S][_S]) % P,
                    g[0][S][_S] = g[1][S][_S] = f[S][_S];
        int _C = (all - 1) ^ a[i].s;
        for (int _S = _C; _S>=0; _S = (_S - 1) & _C){
            int C = (all - 1) ^ _S;
            for (int S = C; S>=0; S = (S - 1) & C){
                g[0][S | a[i].s][_S] = (g[0][S | a[i].s][_S] + g[0][S][_S]) % P;
                g[1][_S][S | a[i].s] = (g[1][_S][S | a[i].s] + g[1][_S][S]) % P;
                if (!S) break;
            }
            if (!_S) break;
        }
    }
    for (int S=0; S<all; S++)
        for (int _S=0; _S<all; _S++)
            ans += g[0][S][_S] + g[1][S][_S] - f[S][_S];
    printf("%lld\n",(ans % P + P) % P);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值