NOI 2015 寿司晚宴 状压DP

为了庆祝NOI的成功开幕,主办方为大家准备了一场寿司晚宴。小G和小W作为参加NOI的选手,也被邀请参加了寿司晚宴。

在晚宴上,主办方为大家提供了 n1 种不同的寿司,编号 1,2,3,,n1 ,其中第种寿司的美味度为 i+1 (即寿司的美味度为从 2 n)。

现在小G和小W希望每人选一些寿司种类来品尝,他们规定一种品尝方案为不和谐的当且仅当:小G品尝的寿司种类中存在一种美味度为x的寿司,小W品尝的寿司中存在一种美味度为y的寿司,而x与y不互质。

现在小G和小W希望统计一共有多少种和谐的品尝寿司的方案(对给定的正整数p取模)。注意一个人可以不吃任何寿司。

题外话:
  上午听某大神讲DP,讲了不少NOI的好(恶心)题(然而基本没有听懂)。结果晚上水NOI2015时就碰到了。
  一开始偷瞄了题目标签,看到状压后很容易想到压质因子,不过看了看顶多拿一半分。感觉应该是 (n2n) 的做法,但细节想不清楚。。

思路:
  果然只要压 n 以内的质因子,对所有数按其大于 n 的因数分组,若没有大于 n 的因数则单独分组。对于小于 n 的因子实际上有3种状态,即某一个人选或者都不选,但这样不是很方便,简单起见我们用 f[i][j] 表示第一个人取的状态为 i ,第二个人取的状态为 j 的方案数。然后一开始就去掉无效状态加以优化(其实不优化也行)。
  然后对于每一组数,显然某个人只要选了其中的一个,另外一个人就一个也不能选,于是我们每次将f复制到 dp[][] ,转移也就不难推了。处理完一组后再令 f[u][v]=dp[u][v]+dp[v][u]f[i][j] ,表示某一个人选或不选这一组数,由于这两种情况都包含了两人都不选的情况,所以还需要减掉重复
  由于小于 n 的质数最多只有8个,所以复杂度为 O(n38)

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>

#define For(i,j,k) for(int i = j;i <= k;i++)
#define Forr(i,j,k) for(int i = j;i >= k;i--)

const int N = 256;
const int Prime[] = {1, 2, 3, 5, 7, 11, 13, 17, 19};

using namespace std;

struct Num{
    int fac, s;

    bool operator < (const Num& A) const{
        return fac < A.fac;
    }

}A[N<<1];

typedef long long LL;

LL f[N][N], dp[N][N], Mod;
int n, Begin[N], Next[N * N], to[N * N], e;

int main(){
    scanf("%d%lld", &n, &Mod);
    For(i,1,n-1){
        int t = i + 1;
        For(j,1,8) while(t % Prime[j] == 0) 
            t /= Prime[j], A[i].s |= 1 << (j - 1);
        A[i].fac = t;
    }
    For(u,0,255) 
        For(v,0,255) 
            if(!(u & v)){
                Next[++e] = Begin[u];
                Begin[u] = e;
                to[e] = v;
            }
    sort(A + 1, A + n);
    f[0][0] = dp[0][0] = 1;
    int l = 1;
    while(l < n){
        int r = l;
        while(A[r+1].fac == A[l].fac && A[l].fac != 1) ++r;
        For(i,l,r)
            Forr(u,255,0) for(int j = Begin[u];j;j = Next[j]){
                int v = to[j];
                if(!(A[i].s & u)) dp[u][v|A[i].s] = 
                    (dp[u][v|A[i].s] + dp[u][v]) % Mod;
            }
        For(u,0,255) 
            for(int j = Begin[u];j;j = Next[j]){
                int v = to[j];
                f[u][v] = 
                    (dp[u][v] + dp[v][u] - f[u][v] + Mod) % Mod;
            }
        For(u,0,255)
            for(int j = Begin[u];j;j = Next[j]) 
                dp[u][to[j]] = f[u][to[j]];
        l = r + 1;
    }
    LL Ans = 0;
    For(u,0,255) For(v,0,255) Ans = (Ans + f[u][v]) % Mod;
    printf("%lld\n", Ans);
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值