冲刺NOI2017 (20) 苹果树 (矩阵树定理 容斥原理 Meet in middle)

来源:互联网 发布:DNF数据芯片是干嘛用的 编辑:程序博客网 时间:2024/05/17 06:10

题目大意

给定n个苹果,对于苹果i,其甜度为cici>=-1。假如ci=-1,代表苹果i是坏的,否则它是好的。

现在要用n1条线把这n个苹果连成一个联通块,也就是一棵树,定义树上一个苹果是有用的,当且仅当它是一个好苹果,且与至少一个好苹果直接相连。一棵树的权值定义为树上的有用的苹果的甜度之和。

给定limit,问有多少种不同的生成树,满足其权值小于等于limit。答案对109+7取模。两个生成树是不同的,当且仅当存在一条边(u,v),满足(u,v)在一棵生成树中出现,在另外一棵中没有出现。

(n<=40)


题解

首先,我们讲生成树上的点分为三种:1.坏点(-1),2.有用的点,3.没用的点

那么,先不考虑点的标号和权值,得到的生成树应该满足以下几个性质:

  • 没用的点只能和坏点连边,
  • 有用的点可以和坏点和除自己之外的有用的点连边,
  • 坏点可以和任何一个除自己的之外的点连边。
  • 有用的点至少和一个其他有用的点连边。

因为考虑最后一个条件的话无法进行构图(或者很难构图),所以我们先只考虑前三个条件进行构图,然后再在后面处理的过程中考虑容斥。

考虑前三个条件:设图中有x个有用的点,y个没用的点,z个坏点。显然,对于一个共有n个点的生成树,由于坏点的数量即z是确定的,则至多有n(x,y,z)的组合。

然后,对于每一个组合按照上面的规则连边:坏点和所有除自己以外的点连边,有用的点和除自己之外的有用的点连边。然后就可以用矩阵树定理求出这个组合下的生成树个数,记有i个有用的点的图此种生成树的个数为G[i]


因为上述的做法没有考虑第四个限制条件,所以我们考虑容斥。说明容斥之前,先明确两个数组的定义:

  • G[i]: 表示不考虑第四个限制条件,有i个有用的点时,不同的生成树的个数;
  • F[i]: 表示考虑第四个限制条件,有i个有用的点时,不同的生成树的个数。

考虑容斥时,在整体中减去不合法的部分。例如,当我们有i个有用的点,且不考虑条件四时,所有这样的生成树中可能有j(0<=j<=i)个有用的点是符合条件四的,而F[i]中必须有i个有用的点满足条件四,易得出:

F(i)=G[0],G[i]i1j=0F[j]Cji,i = 0i > 0


但是上面的过程没有考虑苹果的权值和标号,那接下来就考虑一下。

对于上面我们处理出来的F[i]数组,F[i]所表示的含义为当生成树中有i个有用的点时,不考虑苹果的标号,结构不同的生成树的个数。如果不考虑题面中对生成树权值和的限制limit的话,直接上组合数乘排列数就可以统计出来了,但是这道题对生成树的权值和有限制,就必须选择另一种方法。

考虑枚举,枚举每一个非坏点的苹果是否使用,那么直接就可以处理出数组H[i],表示选i个苹果作为有用苹果的合法方案数,时间复杂度是O(2n)。显然是会超时的。

因为n<=40,所以可以考虑Meet in middle,就是说,可以把前一半苹果和后一半苹果分开来考虑。这样我们可以处理出一个信息数组,每个元素的含义为“这种方案下权值和为num,选用了x个”,然后将前后两个处理出的信息数组排序后发现统计答案时的取值是单调的,处理出前缀和后可以用尺取法统计出H[i]数组。


最后,ans=ni=0F[i]H[i],别忘了取模你就1A了。

太他娘的奇妙了。


代码:

#include <cstdio>#include <iostream>#include <algorithm>#include <cstring>using namespace std;const int moder=int(1e9)+7;inline int les(const int &a,const int &b) {int c=a-b; return c<0?c+moder:c;}inline int add(const int &a,const int &b) {int c=a+b; return c>=moder?c-moder:c;}inline int mul(const int &a,const int &b) {long long c=1ll*a*b; return c%moder;}inline int fpow(int,int);inline int inv(const int &a) {return fpow(a,moder-2);}int fpow(int a,int k) {    register int val=1;    for(;k;k>>=1,a=mul(a,a)) if(k&1) val=mul(val,a);    return val;}const int maxn=55;int n,lit;int f[maxn][maxn];inline void Add_eage(int x,int y) {    if(!f[x][y]) ++f[x][x], ++f[y][y];    f[x][y]=f[y][x]=-1;}//求Kirchhoff矩阵的n-1阶主子式的行列式的绝对值int Gauss(const int n) {    register int i,j,k, res=1;    for(i=1;i<=n;++i) {        if(!f[i][i]) {            res=-res;            for(j=i+1;j<=n;++j) if(f[j][i]) break;            for(k=1;k<=n;++k) swap(f[i][k],f[j][k]);        }        res=mul(res,f[i][i]);        for(j=i+1;j<=n;++j) {            int ml=mul(f[j][i],inv(f[i][i]));            for(k=i;k<=n;++k) f[j][k]=les(f[j][k],mul(f[i][k],ml));        }    }    return res;}//构造Kirchhoff矩阵void Construct(const int &x,const int &y,const int &z) {    register int i,j;    memset(f,0,sizeof f);    for(i=x+y+1;i<=n;++i)        for(j=1;j<=n;++j) if(i!=j)            Add_eage(i,j);    for(i=1;i<=x;++i)        for(j=1;j<=x;++j) if(i!=j)            Add_eage(i,j);    return;}int Fact[maxn], Inv[maxn];inline int C(const int &n,const int &m) {return mul(Fact[n],mul(Inv[m],Inv[n-m]));}void Init() {    Fact[0]=Inv[0]=1;    register int i;    for(i=1;i<maxn;++i) Fact[i]=mul(Fact[i-1],i), Inv[i]=inv(Fact[i]);    return;}pair<int,int> s1[int(1.5e6)], s2[int(1.5e6)];int top1, top2;int pre[int(1.5e6)][maxn];//Meet in middlevoid MiM(int *w,int n,pair<int,int> *sta,int &top) {    register int mask,tmp,num,i;    for(mask=0;mask<(1<<n);++mask) {        long long sum=0;        for(tmp=mask,num=0,i=1;tmp && sum<=lit;tmp>>=1,++i) if(tmp&1 && ~w[i])            sum+=w[i], ++num;        if(sum>lit) continue;        sta[++top]=make_pair(sum,num);    }    return;}int c[maxn];int G[maxn], F[maxn];int h[maxn];int main() {#ifndef ONLINE_JUDGE    freopen("apple.in","r",stdin);    freopen("apple.out","w",stdout);#endif    register int i,j;    Init();    scanf("%d%d",&n,&lit);    for(i=1;i<=n;++i) scanf("%d",&c[i]);    sort(c+1,c+1+n);    int num=0;    while(!~c[num+1]) num++;    //预处理G[]    for(i=0;i<=n-num;++i) {        Construct(i,n-num-i,num);        G[i]=Gauss(n-1);    }    //预处理F[]    F[0]=G[0];    for(i=1;i<=n-num;++i) {        F[i]=add(F[i],G[i]);        for(j=0;j<i;++j) F[i]=les(F[i],mul(F[j],C(i,j)));    }    int len1=(n-num)>>1, len2=n-num-len1;    MiM(c+num,len1,s1,top1);    sort(s1+1,s1+1+top1);    MiM(c+num+len1,len2,s2,top2);    sort(s2+1,s2+1+top2);    //处理出前缀和数组    for(i=1;i<=top2;++i)        ++pre[i][s2[i].second];    for(j=0;j<=n;++j)        for(i=1;i<=top2;++i)            pre[i][j]+=pre[i-1][j];    //利用单调性,尺取法统计h[]    int p1=1, p2=1;    while(p2<=top2 && s1[p1].first+s2[p2].first<=lit) ++p2; --p2;    for(p1=1;p1<=top1;++p1) {        while(p2 && s1[p1].first>lit-s2[p2].first) --p2;        for(i=0;i<=n;++i) h[s1[p1].second+i]=add(h[s1[p1].second+i],pre[p2][i]);    }    int ans=0;    for(i=0;i<=n;++i) ans=add(ans,mul(F[i],h[i]));    printf("%d\n",ans);    return 0;}

这里写图片描述

阅读全文
1 0
原创粉丝点击