【Codeforces Round #431 (Div. 1) D.Shake It!】

·最小割和组合数放在了一起,产生了这道题目

英文题,述大意;
    
一张初始化为仅有一个起点0,一个终点1和一条边的图。输入n,m表示n次操作(1<=n,m<=50),每次操作是任选一条已存在的边,新建一个编号为(n+1)的节点并向这条边的两个端点连边(共连接两条边)。输入n次操作后满足最小割为m的图有多少种。此处两个图相同当且仅当两边集和点集满足双射。答案取模109+7。

分析:

     本题要求方案数。首先读题较困难之处是什么样的两个图算不同的方案。知道每次加入的点是有编号的,有编号的点是独一无二的。在这里画一些图以便清晰理解,以下两个图的情况均为两种方案而不是一种:

image

      回到题目要求,最苛刻的无非是加入了最小割的限制。因此先试放宽要求,不考虑最小割的限制。那么,假如当前进行了n次操作,那么图中的边数可以算出来等于(1+2*n),那么第(n+1)次操作所添加的点共有(1+2*n)种添加方式。没有最小割,这个思路将非常棒。

      但是你可以发现,如果添上最小割,上述方法就不可行了。分析失败的原因:每次添加一个点到随意地一个位置的方法难以维护当前图的最小割。因此,我们需要寻找一种方法,既可以通过组合计算方案数(因为这道题不可能枚举每种图,所以计算组合方案是必要的)又可以保证能够在保持最小割为某个值的情况下进行方案数运算。   

      对于上述需求,我们可以想到,为了能够保持某些性质(最小割),可以通过枚举子问题来实现。所谓子问题就是将原来的问题化成几个规模较小的但是形式和性质相同的问题加以解决(如动态规划利用了子问题的性质)。

     本题的图怎样拆成子问题呢?入手点可以选择为最早的点数为3的图。我们发现,这个点数为3的图可以由这些部分组成:

                               image

              这里面已经有了子问题的影子。观察任意一条新添边和其两端点组成的子图,其实就是操作数为0的时候的图(即只有起点终点的图),也就是说,我们正尝试把当前图拆为一条主干边及两端点和一些操作数较小的子图。我们来欣赏这个样例吧:

                        image

         为什么偏要分成两部分?原因是可以快速维护最小割。该图的最小割就等于min{子问题1图的最小割,子问题2图的最小割}。我们可以将这样一对子问题看成在主干边上建一座大桥。我们知道主干边上是可以建很多座大桥的:

                         image

     图为三座大桥。当多座桥存在的时候,最小割的计算方式为每座桥的最小割相加。得到这个结论后可以发现,一个图由主干边加很多桥组成,由于图的点数大于这些桥的点数,因此从点数由小到大的递推状态转移具有可行性。

     想想递推求方案数。令:

     f[i][j]表示当前进行了i次操作(即有(i+2)个点),图的最小割为j的种类数

     g[i][j]表示当前进行了i次操作,最小割为j的大桥的种类数

     这个状态看上去g[i][j]是专门为f[i][j]服务的。那好吧就先这样认为吧,因为上文讲的内容全是用各种各样的桥构造当前图。先别急桥怎么处理,不过要记住一点:一个桥的本质是一个子问题二元组(见上文桥的来源就知道了)。

     首先想一个填表式的伪状态转移方程:

      f[这个大大的图]+=f[这个大大地图-某座桥]*g[这桥]

    画图可以表示为:

image

      接下来我们一步步细化方程式。设图的操作数为i,最小割为j。

[第一步]将上式子转化为较为正统的方程式:

      设枚举的桥操作数为i,最小割为j。

             f[x][y]+=f[x-i][x-j]*g[i][j]

[第二步]由于同种桥可以添加多次,所以完善方程式:
      设当前操作数为i,最小割为j的桥将被使用T次。

             f[x][y]+=f[x-i*T][y-j*T]*MultiCombination(g[i][j],T)

      我们知道操作数为i,最小割为j的桥共有g[i][j]种,此处我们要从中选出T座桥,由于相同桥可以多次选择,因此问题转化为在g[i][j]中选出T个桥,一个桥可以被多次选择的方案数,即MultiCombination,其值等于C(g[i][j]+T-1,T)

[第三步]为了做到能够边转移边递推多重组合(MC),将方程转为刷表法

      设当前操作数为i,最小割为j的桥将被使用T次。

             f[x+i*T][y+j*T]+=f[i][j]*MultiCombination(g[i][j],T)

      转化形式的好处就是我们可以顺次枚举T,那么可以使用一个comb来表示当前C(g[i][j]+T-1,T)的值,若T变为(T+1),那么可以直接递推:

             comb=comb*(g[i][j]+T)/T

[合法性证明]刷表法到达i,j的时候,由于小于i,j的子问题已经贡献了答案,因此此时f[i][j]已经是正确方案数,用它来更新更大的状态不会造成遗漏。

      至此,我们已经成功地借助g[][]完成了对f[][]的递推。该想想g[i][j]怎么求了。我们回到最初,看看桥是什么东西——桥指的是两个满足条件的图A,B并且对于当前的大图,其中一个图和这个大图起点重合,另一个图和这个大图终点重合,然后第一个图的终点和第二个图的起点重合:(回顾一下吧)

                                                    image

            然后就很奇妙:我们发现两个子问题图的操作数一定小于大图的操作数,也就是说满足从大到小的关系——小图的f[][]不已经求出来了吗?因此桥是需要由组成它的两个子图来计算方案数的。在本文开始的时候提到过怎么样的两个图算是不同,这里我们计算g[i][j]的方法是,先固定左边的子问题图的最小割等于j,然后右边的图最小割随意为多少,并且保证两个图的操作数加起来等于i-1(-1的原因画图可知),然后左右反过来进行一次:    

      g[i][j]+=f[a][j]*f[i-1-b][u] (u>=j)

      g[i][j]+=f[a][u]*f[i-1-b][j] (u> j)

     总结来说,本题的核心在于寻找子问题从而求解方案数,同时结合了本题的加边特点维护最小割。代码在这里:    

 

 1 #include<stdio.h>
 2 #include<cstring>
 3 #define ll long long
 4 #define M 1000000007
 5 #define go(i,a,b) for(int i=a;i<=b;i++)
 6 const int N=60;int n,m;
 7 ll f[N][N],g[N][N],t[N][N],INV[N];
 8 int main()
 9 {
10     scanf("%d%d",&n,&m);f[0][1]=1;
11     INV[1]=1;go(i,2,60)INV[i]=(-M/i*INV[M%i])%M;
12     
13     go(i,1,n)go(j,1,i+1)
14     {
15         go(a,0,i-1){int b=i-1-a;
16         go(u, j ,a+1)(g[i][j]+=f[a][u]*f[b][j])%=M;
17         go(u,j+1,b+1)(g[i][j]+=f[a][j]*f[b][u])%=M;}    
18             
19         memset(t,0,sizeof(t));
20             
21         go(x,0,n)go(y,1,x+1){ll comb=1;
22         go(T,1,n){if(x+T*i>n)break;
23         (comb*=(g[i][j]+T-1)%M*INV[T]%M)%=M;
24         (t[x+i*T][y+j*T]+=comb*f[x][y]%M)%=M;}}
25             
26         go(x,0,n)go(y,1,x+1)(f[x][y]+=t[x][y])%=M;
27             
28     }
29     printf("%d",(f[n][m]%M+M)%M);return 0;
30 }//Paul_Guderian

 

 

 

 

  

   

 

   

    

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

A never-ending, fast-changing and dream-like world unfolds,

as the secret door opens…… ——————本题题干节选

posted @ 2017-10-31 18:03  大米饼  阅读(307)  评论(0编辑  收藏  举报