CEOI2007 Treasury

题目描述

给一棵树,你可以匹配有边相连的两个点,问你这棵树的最大匹配是多少,并且计算出有多少种最大匹配。

解法

容易想到将边的匹配转换到点上面,用 \(dp_{u,0/1}\) 表示以节点 \(u\) 为根节点的子树中,不选/选 \(u\) 节点来匹配得到的最大匹配数。

那么当 \(u\) 没有被选时,子节点选不选都无所谓

我们令

\[f_u=max\{dp_{u,0},dp_{u,1}\} \]

则有

\[dp_{u,0}=\sum_{v\in son(u)} f_v \]

若选择 \(u\),则只有被选的那条边的那个子节点必不能选,其他节点选不选无所谓,则

\[dp_{u,1}=max\{dp_{u,0}-f_v+dp_{v,0}+1\} \]

第一问这样做已经可以了,难点在于第二问——记录方案。

由第一问,我们得到启发,用 \(g_{u,0/1}\) 表示以节点 \(u\) 为根节点的子树中,不选/选 \(u\) 节点来匹配得到的最大匹配数的方案数。

若不选择 \(u\) 节点,则由乘法原理将所有的子节点对应的方案的方案数乘起来即可

\[h_u=\begin{cases} g_{u,0}, & dp_{u,0}>dp_{u,1} \\ g_{u,0}+g_{u,1}, & dp_{u,0}=dp_{u,1}\\ g_{u,1}, & dp_{u,0}<dp_{u,1} \end{cases}\]

\[g_{u,0}=\prod_{v\in son(u)} h_v \]

而对于 \(g_{u,1}\) 我们需要在更新 \(dp_{u,1}\) 的时候同时更新它。假设我们当前选的边为 \(u\to v\) ,那么除了 \(v\) 之外的其它子节点都可以任意选,而不选 \(v\) 节点对应的方案数为 \(g_{v,0}\),那么总的方案数为 \(g_{u,0}/h_v\times g_{v,0}\)。若 \(dp_{u,1}<dp_{u,0}-f_v+dp_{v,0}+1\),即
我们找到了一个更大的匹配,那么直接将 \(g_{u,1}\) 更新为 \(g_{u,0}/h_v\times g_{v,0}\);若 \(dp_{u,1}=dp_{u,0}-f_v+dp_{v,0}+1\),即我们找到了一个和最大匹配方案数相同的方案,那么将 \(g_{u,1}\) 累加上这个值即可。

for(int i=head[u];i;i=e[i].next){
    int v=e[i].to;
    ll ret=dp[u][0]-f[v]+dp[v][0]+1;
    ll tmp=g[u][0]/h[v]*g[v][0];
    if(dp[u][1]<ret) dp[u][1]=ret,g[u][1]=tmp;
    else if(dp[u][1]==ret) g[u][1]+=tmp;
}

Tips

我们发现最后的方案数超过了 \(long long\),需要些高精。然而方程中带了除法QAQ,此时需要一些转换。观察方程,发现所求值是一个总积除上中间一个数的形式,实际上可以把其转换为一个前缀积和一个后缀积相乘,完美的避开了除法。(如果你不想再重载减法的话,也可以把其转换为前缀和加后缀和)

#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;
#define N 1007

inline int read(){
    int x=0,flag=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')flag=0;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
    return flag? x:-x;
}

inline int max(int x,int y){return x>y? x:y;}

struct BigNum{
    int n,s[207];
    
    BigNum(int x=0){
        n=0;
        memset(s,0,sizeof(s));
        while(x) s[++n]=x%10,x/=10;
    }
    
    BigNum operator *(const BigNum B){
        BigNum c;
        c.n=B.n+n-1;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=B.n;j++){
                c.s[i+j-1]+=s[i]*B.s[j];
                c.s[i+j]+=c.s[i+j-1]/10;
                c.s[i+j-1]%=10;
            }
        while(c.s[c.n+1]) c.n++;
        while(c.n>=1&&(!c.s[c.n])) c.n--;
        return c;
    }
    
    BigNum operator +(const BigNum B){
        BigNum c;
        c.n=max(B.n,n);
        for(int i=1;i<=c.n;i++){
            c.s[i]+=s[i]+B.s[i];
            c.s[i+1]=c.s[i]/10;
            c.s[i]%=10;
        }
        while(c.s[c.n+1]) c.n++;
        while(c.n>=1&&(!c.s[c.n])) c.n--;
        return c;
    }
    
    bool operator <(const BigNum B){
        if(n!=B.n) return n<B.n;
        for(int i=n;i>=1;i--)
            if(s[i]!=B.s[i]) return s[i]<B.s[i];
        return 0;
    }
    
    bool operator ==(BigNum B){
        return !(((*this)<B)|(B<(*this)));
    }
    
    void print(){
        if(!n) putchar('0');
        else for(int i=n;i>=1;i--) putchar(s[i]+'0');
        putchar('\n');
    }
    
}f[N],g[N][2],h[N],dp[N][2],pre[N],suf[N],pres[N],sufs[N];

int n;
bool vis[N];
vector<int> G[N];

void dfs(int u){
    vis[u]=1,g[u][0]=BigNum(1);
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i];
        if(!vis[v]) dfs(v);
        dp[u][0]=dp[u][0]+f[v];
        g[u][0]=g[u][0]*h[v];
    }
    pre[0]=BigNum(1);
    pres[0]=BigNum(0);
    for(int i=0;i<G[u].size();i++)
        pres[i+1]=pres[i]+f[G[u][i]],
        pre[i+1]=pre[i]*h[G[u][i]];
    suf[G[u].size()+1]=BigNum(1);
    sufs[G[u].size()+1]=BigNum(0);
    for(int i=G[u].size()-1;~i;i--)
        sufs[i+1]=sufs[i+2]+f[G[u][i]],
        suf[i+1]=suf[i+2]*h[G[u][i]];
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i];
        BigNum r(1);
        BigNum ret=pres[i]+sufs[i+2]+dp[v][0]+r;
        BigNum tmp=pre[i]*suf[i+2]*g[v][0];
        if(dp[u][1]<ret) dp[u][1]=ret,g[u][1]=tmp;
        else if(dp[u][1]==ret) g[u][1]=g[u][1]+tmp;
    }
    if(dp[u][0]<dp[u][1]) f[u]=dp[u][1],h[u]=g[u][1];
    else if(dp[u][0]==dp[u][1]) f[u]=dp[u][0],h[u]=g[u][0]+g[u][1];
    else f[u]=dp[u][0],h[u]=g[u][0];
}

signed main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++){
        int u=read(),op=read();
        for(int i=1;i<=op;i++)
            G[u].push_back(read());
    }
    BigNum ans(0),ret(1);
    for(int i=1;i<=n;i++){
        if(!vis[i]) dfs(i);
        if(ans<f[i]) ans=f[i],ret=h[i];
    }
    ans.print(),ret.print(); 
}
posted @ 2020-10-24 14:55  Kreap  阅读(83)  评论(0编辑  收藏  举报