COCI2012 TOY

m种物品,n个箱子之中装着若干物品。问取出一些箱子后,所有m种物品都被选出的方案数。

m<=20,n<=106

这道题很妙啊 深刻地利用了容斥

看到n=20,我们就想到了状压和容斥。

怎么容斥呢?我们设A(s)表示状态为s的集合的数量,那么A(2^m-1)就是所求答案。但是这个不好做,那么我们用容斥放缩一下这个条件。我们设B(s)表示状态为s的子集的集合的数量,那么我们可以得到以111为例 B(111)=A(111)+B(110)+B(101)+B(011)-B(100)-B(010)-B(001)+B(000) 那么化简一下 A(111)=B(111)-B(110)-B(101)-B(011)+B(100)+B(010)+B(001)-B(000) 标准的容斥形式。那么剩下的问题就是求出B的值。

我们定义B(s)是s的子集的集合的数量,那么我们只要枚举s的子集就行了,统计数量只要在读入时统计一下就行了。但是枚举子集的复杂度是3^m,过不去,那么我们再优化。

这里我们用了一种分治的方法来优化复杂度,我们利用一种类似cdq分治的办法,把复杂度优化到m*2^m。具体见代码。

然后就是容斥了。

这里证明一下子集枚举的复杂度:我们不妨设二元对(s,t)。那么我们不妨用三进制来表达关系,0s是t的子集,1s和t没关系,2t是s的子集。那么这就是3^m了。

还有一种可以省去popcount的枚举子集的方法,就是用递归,但是程序中没有实现,想知道的可以留个言教导一下蒟蒻。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100010, mod = 1000000007;
int n, all, m;
ll ans; 
int c[N], d[N];
inline ll power(ll x, ll t)
{
    ll ret = 1;
    for(; t; t >>= 1, x = x * x % mod) if(t & 1) ret = ret * x % mod;
    return ret;
}
void build(int l, int r)
{
    if(l > r) return;
    if(l == r) { c[l] = d[l]; return; }
    int mid = (l + r) >> 1;
    build(l, mid); build(mid + 1, r);
    for(int i = l; i <= mid; ++i) c[i + mid - l + 1] = (c[i + mid - l + 1] + c[i]) % mod; 
}
int main()
{
    scanf("%d%d", &n, &m); all = 1 << m;
    for(int i = 1; i <= n; ++i)
    {
        int num, tot = 0; scanf("%d", &num);
        while(num--)
        {
            int x; scanf("%d", &x); --x;
            tot |= (1 << x);
        }
        ++d[tot]; 
    }
    build(0, all - 1);
    for(int i = 0; i < all; ++i) 
    {
        int x = __builtin_popcount(i);
        if((x % 2) == (m % 2)) ans = (ans + power(2, c[i])) % mod; else ans = ((ans - power(2, c[i])) % mod + mod) % mod;
    }
    printf("%lld\n", ans);
    return 0;
}

 

posted @ 2017-04-09 21:09  19992147  阅读(229)  评论(0编辑  收藏  举报