CodeForces 81 E.Pairs(树形DP)

Description

给出 n n 个点n个边的有向图,每个点的出度为 1 1 且有颜色(黑白),求该图的最大匹配数,以及达到最大匹配后要让异色匹配数最大

Input

第一行一整数n表示点数,之后 n n 行每行输入两个整数fi,si表示 i i 节点的父亲节点和颜色(2n105)

Output

输出最大匹配数以及最大异色匹配数,同时输出具体匹配方案

Sample Input

5
5 2
3 2
5 1
2 1
4 2

Sample Output

2 2
5 3
4 2

Solution

由于边数也为 n n 且每点出度为1,故该图的每个连通分支要么是树要么是基环,任取基环上一条边,删去该条边后基环变成树,树上该问题相对简单,树形 DP D P 即可求解。但注意到这样不是最优的,因为删去的这条边可能是最优方案的一条边,但是注意到只要选择了这条边,那么基环上这条边相邻的一条边必然不会被选到,那么断掉这条边后问题又是树上问题,故做两遍树上的匹配取最优值即为该基环上的最优匹配

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f,maxn=100005;
struct P
{
    int x,y;
    P(){};
    P(int _x,int _y){x=_x,y=_y;}
    P operator+(const P&b)const{return P(x+b.x,y+b.y);}
    P operator-(const P&b)const{return P(x-b.x,y-b.y);}
    bool operator<(const P&b)
    {
        if(x!=b.x)return x<b.x;
        return y<b.y;
    }
};
int n,f[maxn],s[maxn],vis[maxn],son[maxn],root;
vector<int>g[maxn];
P dp[maxn][2],ans;
vector<P>vec,temp;
void dfs(int u)
{
    dp[u][0]=dp[u][1]=P(0,0);
    vis[u]=1,son[u]=0;
    P res=P(0,0);
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==root)continue;
        dfs(v);
        dp[u][1]=dp[u][1]+dp[v][0];
        P now=P(1,s[u]^s[v])+dp[v][1]-dp[v][0];
        if(res<now)res=now,son[u]=v;
    }
    dp[u][0]=dp[u][1]+res;
}
void path(int u,int sta)
{
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==root)continue;
        if(sta==1)path(v,0);
        else if(son[u]==v)temp.push_back(P(u,v)),path(v,1);
        else path(v,0);
    }
}
void Deal(int u)
{
    while(!vis[u])vis[u]=1,u=f[u];
    P res=P(0,0);
    for(int i=0;i<2;i++)
    {
        root=u;
        dfs(root);
        if(res<dp[root][0])
        {
            res=dp[root][0];
            temp.clear();
            path(root,0);
        }
        u=f[u];
    }
    ans=ans+res;
    for(int i=0;i<temp.size();i++)vec.push_back(temp[i]);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&f[i],&s[i]);
        s[i]--;
        g[f[i]].push_back(i);
    }
    for(int i=1;i<=n;i++)
        if(!vis[i])Deal(i);
    printf("%d %d\n",ans.x,ans.y);
    for(int i=0;i<vec.size();i++)printf("%d %d\n",vec[i].x,vec[i].y);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值