同桌的你

题目描述

这里写图片描述

数据范围n<=10^6

dp

i喜欢j则i向j连边。最终我们会得到若干个环套树。
设f[i][1]表示i和某个儿子配对的最优答案,f[i][0]表示i不与任何儿子配对的最优答案,这在树中很容易做。
然而可以发现,如果i和某个儿子j配对,则i的父亲和i的不会配对,j也不会和任何儿子配对。所以我们可以任意找环上的一条边,去掉它dp一遍,再加回来去掉它的一条邻边再dp一遍即可。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxn=1000000+5;
int t,i,j,cnt,n,k[maxn],next[maxn*2],g[maxn*2],num,d[maxn],bz[maxn],bz1,st[maxn],en[maxn],p[maxn],p1[maxn],fa[maxn];
struct ar{
    bool friend operator <(ar x,ar y){
        return ((x.a<y.a)||((x.a==y.a)&&(x.b<y.b)));
    }
    ar friend operator +(ar x,ar y){
        ar c;c.a=x.a+y.a,c.b=x.b+y.b;
        return c;   
    }
    ar friend operator -(ar x,ar y){
        ar c;c.a=x.a-y.a,c.b=x.b-y.b;
        return c;   
    }
    int a,b;
}a[maxn],dp[maxn][2],dp1[maxn][2],b[2],ans,an[maxn];
void read(int &n){
    char c=getchar();
    while ((c<'0')||(c>'9')) c=getchar();n=0;
    while ((c>='0')&&(c<='9')) n=n*10+c-'0',c=getchar();
}
void add(int x,int y){
    next[++num]=k[x];g[num]=y;
    k[x]=num;
}
int main(){
    read(t);
    while (t){
        t--;
        ans.a=ans.b=0;
        memset(dp,0,sizeof(dp));
        memset(k,0,sizeof(k));
        memset(bz,0,sizeof(bz));
        memset(fa,0,sizeof(fa));
        memset(dp1,0,sizeof(dp1));num=cnt=0;bz1=0;
        read(n);
        fo(i,1,n) {
            read(a[i].a),read(a[i].b);
            add(a[i].a,i),add(i,a[i].a);
        }
        fo(i,1,n) if (bz[i]==0) {
            memset(b,0,sizeof(b));
            d[0]=1;d[1]=i;j=0;bz[i]=++bz1;
            while (j<d[0]){
                j++;int x=d[j],l=k[x];
                while (l){
                    if (bz[g[l]]!=bz1){
                        fa[g[l]]=x;
                        bz[g[l]]=bz1,d[++d[0]]=g[l];
                    }else if (fa[x]!=g[l]&&(fa[g[l]]!=x)){
                        b[0].a=x,b[0].b=fa[x];
                        b[1].a=x,b[1].b=g[l];
                    }
                    l=next[l];
                }if (b[1].a) break;
            }
            d[0]=1;d[1]=i;j=0;bz[i]=++bz1;
            while (j<d[0]){
                j++;int x=d[j],l=k[x];
                st[x]=d[0]+1;
                while (l){
                    if ((x==b[0].b)&&(g[l]==b[0].a)) {l=next[l];continue;}
                    if ((x==b[0].a)&&(g[l]==b[0].b)) {l=next[l];continue;}
                    if (bz[g[l]]!=bz1) {
                        bz[g[l]]=bz1,d[++d[0]]=g[l];
                    }l=next[l];
                }en[x]=d[0];
            }
            fod(j,d[0],1){
                int x=d[j],l;
                fo(l,st[x],en[x]) dp[x][0]=dp[x][0]+dp[d[l]][1];
                ar c,w;c.a=1;dp[x][1]=dp[x][0];p[x]=0;
                fo(l,st[x],en[x]) {
                    c.b=(a[x].b!=a[d[l]].b);
                    w=dp[x][0]-dp[d[l]][1]+dp[d[l]][0]+c;
                    if (dp[x][1]<w) {
                        dp[x][1]=w;
                        p[x]=d[l];
                    }
                }
            }bz1++;
            ans=ans+dp[i][1];
            fo(j,1,d[0]) {
                if (bz[d[j]]!=bz1) {
                    if (p[d[j]]>0) an[++cnt].a=d[j],an[cnt].b=p[d[j]],bz[p[d[j]]]=bz1;
                }
            }
            d[0]=1;d[1]=i;j=0;bz[i]=++bz1;
            while (j<d[0]){
                j++;int x=d[j],l=k[x];
                st[x]=d[0]+1;
                while (l){
                    if ((x==b[1].b)&&(g[l]==b[1].a)) {l=next[l];continue;}
                    if ((x==b[1].a)&&(g[l]==b[1].b)) {l=next[l];continue;}
                    if (bz[g[l]]!=bz1) {
                        bz[g[l]]=bz1,d[++d[0]]=g[l];
                    }l=next[l];
                }en[x]=d[0];
            }
            fod(j,d[0],1){
                int x=d[j],l;
                fo(l,st[x],en[x]) dp1[x][0]=dp1[x][0]+dp1[d[l]][1];
                ar c,w;c.a=1;dp1[x][1]=dp1[x][0];p1[x]=0;
                fo(l,st[x],en[x]) {
                    c.b=(a[x].b!=a[d[l]].b);
                    w=dp1[x][0]-dp1[d[l]][1]+dp1[d[l]][0]+c;
                    if (dp1[x][1]<w) {
                        dp1[x][1]=w;
                        p1[x]=d[l];
                    }
                }
            }
            bz1++;
            if (dp[i][1]<dp1[i][1]){
                ans=ans-dp[i][1]+dp1[i][1];
                cnt-=dp[i][1].a;
                fo(j,1,d[0]) {
                    if (bz[d[j]]!=bz1) {
                        if (p1[d[j]]>0) an[++cnt].a=d[j],an[cnt].b=p1[d[j]],bz[p1[d[j]]]=bz1;
                    }
                }
            }
        }
        printf("%d %d\n",ans.a,ans.b);
        fo(i,1,cnt) printf("%d %d\n",an[i].a,an[i].b);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值