[置顶] 准备复赛

【问题描述】

今年的 NOIP 初赛真是简单,小可可不用吹灰之力就考进了复赛,但是复赛可没有
那么简单了,小可可想要好好准备复赛,争取复赛拿个省一。今天小可可在复习树和图
的最大匹配时就碰到这样的一个难题:n 个节点满足以下性质的不同的树有多少种。
1、树是有标号的,每个节点被标上 1 到 n 之间的整数;
2、每个节点最多和其他 3 个节点相连,但是 1 号节点最多和其他 2 个节点相连;
3、这棵树的最大匹配(把树看成二分图后的最大匹配)数为 k。
两棵树被认为不同当且仅当存在两个点 u、v,在一棵树中 u、v 之间有边,另一棵
树中 u、v 之间没边。
由于答案可能很大,所以小可可让你输出答案模 1000000007 (10 9 + 7)。

【输入格式】

第一行包含两个正整数 n,k。

【输出格式】

包含一行,为方案数。

【样例】

exam.in
4 2
exam.out
12

【数据范围】

对于30%的数据,2≤n≤5;
对于60%的数据,2≤n≤20;
对于 100%的数据,2≤n≤50。

【题解】

算法一:
可以枚举所有情况
时间复杂度 O(……)
期望得分:30 分。
算法二:
我们可以用动态规划来解决这个问题,我们知道算树的最大匹配可以用 dp,
设状态为 dp[v][used](v 表示节点编号,used 表示 v 这个点是否被匹配)。而这
里我们设这道题的状态为 f[n][dp0][dp1]表示有 n 个节点满足条件的有根树的个
数,其中 dp0=dp[root][0],dp1=dp[root][1],转移时我们枚举 i 作为一个子树的大
小,和两个子树的 dp0 和 dp1 值,记一个为 m0,m1,另一个记为 n0,n1,则可
dp0
推 知全树的和dp1值为s0=max(m0,m1)+max(n0,n1) ,s1=max(m0+n1,m1+n0,m0+n0)+1。
dp[n][s0][s1]=Σdp [i][m0][m1]*dp[n-i-1][n0][n1]
然后暴力实现。
时间复杂度 O ( n 6 )
期望得分:60 分。
算法三:
在算法二的基础上,我们可以发现如果是最大匹配 dp0 和 dp1 相差不会超过
1,所以,我们可以将状态压缩成 f[n][dp0][dif],dif 表示 dp1 与 dp0 的差值。
时间复杂度 O ( n 4 )
期望得分:100 分。

考试的时候我连二分图最大匹配都不知道。

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <cmath>
#include <ctime>
#include <algorithm>
#include <vector>
#include <set>
#include <queue>
#include <map>
#include <bitset>
using namespace std;
typedef long long ll;
ll geti(){
    ll ret=0;char ch=getchar(),k=1;
    while((ch<'0' || ch>'9') && ch!='-')ch=getchar();
    if(ch=='-')k=0,ch=getchar();
    while(ch>='0' && ch<='9')ret=ret*10+ch-'0',ch=getchar();
    return k?ret:-ret;
}
const int N = 100;
const int mo = 1e9 + 7;
int n,k;
ll f[N][N],g[N][N],c[N][N],inv2;
ll ksm (ll a, ll b){
    ll ret = 1;
    while(b) {
        if(b&1) ret = ret*a%mo;
        a = a*a%mo; b>>=1;
    }
    return ret;
}
int main(){
    freopen("exam.in","r",stdin);
    freopen("exam.out","w",stdout);
    n=geti(); k=geti();
    for(int i=0;i<=n;i+=1) {
        c[i][0] = 1;
        for(int j=1;j<=i;j+=1) {
            c[i][j] = (c[i-1][j] + c[i-1][j-1]) % mo;
        }
    }
    g[0][0]=1;// f[1][0]=1;
    inv2 = ksm(2,mo-2);
    for(int i=1;i<=n;i+=1) {
        for(int j=0;j<=i/2;j+=1) {
            for(int k=0;k<=(i-1)/2;k+=1) {
                for(int s=0;s<=j;s+=1) {
                    int a = (k!=0?k:1);
                    int b = ((i-1-k)!=0?(i-1-k):1);
                    if(k!=i-1-k || k==0) {
                        f[i][j] += g[k][s]*g[i-1-k][j-s]%mo*a%mo*b%mo*c[i-1][k]%mo;
                    } else {
                        f[i][j] += g[k][s]*g[i-1-k][j-s]%mo*a%mo*b%mo*c[i-1][k]%mo*inv2%mo;
                    }
                    f[i][j] %= mo;
                    if(j-1-s>=0) {
                        if(k!=i-1-k){
                            g[i][j] +=
                                f[k][s]*g[i-1-k][j-1-s]%mo*a%mo*b%mo*c[i-1][k]%mo+
                                g[k][s]*f[i-1-k][j-1-s]%mo*a%mo*b%mo*c[i-1][k]%mo+
                                f[k][s]*f[i-1-k][j-1-s]%mo*a%mo*b%mo*c[i-1][k]%mo;
                            g[i][j] %= mo;
                        } else {
                            g[i][j] +=
                                (
                                 f[k][s]*g[i-1-k][j-1-s]%mo*a%mo*b%mo*c[i-1][k]%mo+
                                 g[k][s]*f[i-1-k][j-1-s]%mo*a%mo*b%mo*c[i-1][k]%mo+
                                 f[k][s]*f[i-1-k][j-1-s]%mo*a%mo*b%mo*c[i-1][k]%mo
                                 )*inv2%mo;
                            g[i][j] %= mo;
                        }

                    }
                }
            }
        }
    }
    cout << (f[n][k]+g[n][k])%mo;
    return 0;
}