zoukankan      html  css  js  c++  java
  • CEOI2007 Treasury

    题目描述

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

    解法

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

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

    我们令

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

    则有

    [dp_{u,0}=sum_{vin 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=egin{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_{vin son(u)} h_v ]

    而对于 (g_{u,1}) 我们需要在更新 (dp_{u,1}) 的时候同时更新它。假设我们当前选的边为 (u o v) ,那么除了 (v) 之外的其它子节点都可以任意选,而不选 (v) 节点对应的方案数为 (g_{v,0}),那么总的方案数为 (g_{u,0}/h_v imes g_{v,0})。若 (dp_{u,1}<dp_{u,0}-f_v+dp_{v,0}+1),即
    我们找到了一个更大的匹配,那么直接将 (g_{u,1}) 更新为 (g_{u,0}/h_v imes 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('
    ');
        }
        
    }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(); 
    }
    
  • 相关阅读:
    CodeForces 347B Fixed Points (水题)
    CodeForces 347A Difference Row (水题)
    CodeForces 346A Alice and Bob (数学最大公约数)
    CodeForces 474C Captain Marmot (数学,旋转,暴力)
    CodeForces 474B Worms (水题,二分)
    CodeForces 474A Keyboard (水题)
    压力测试学习(一)
    算法学习(一)五个常用算法概念了解
    C#语言规范
    异常System.Threading.Thread.AbortInternal
  • 原文地址:https://www.cnblogs.com/wwlwQWQ/p/13869496.html
Copyright © 2011-2022 走看看