您的位置:首页 > 其它

JZOJ4760. 【雅礼联考GDOI2017模拟9.4】同桌的你

2016-09-06 17:11 295 查看

题目大意

给你一堆环套树,n个点。每个点有一个性别,有边相连的点可以配对,每个点只能配对一次。求最多配对数,以及在此情况下最多的不同性别配对数,以及方案。数据有T组。

N≤1000000,T≤3.

分析

考虑没有环的情况,就是树形DP嘛,F[i][0\1]表示这个点有没有和某个儿子配对。随便搞搞

有环怎么办呢?考虑多出来的那条边,设连接了点X,Y,若X与Y不配对,那么这条边就没有用了,连F数组也不用它传递;否则环上的另一条边Z—>X或Y就没有用。那么我们只用选择性屏蔽一条边,分别做两次DP就可以了。

代码

写得挺丑

#include<cstdio>
#include<algorithm>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
const int N=1100005;
struct rec
{
int c,cp;
}a2,b2,tmp,tp,tp1,ans,g
[2],dur;
int tt,b[N*2],next[N*2],first
,d
,e
,fa
,dis
,dad
,a
,pd
,dl
,dd
,q1,q2,w1,w2,t9,p,ban,x,sex
,sig
,kan;
int n,i,j,k,D,E,T;
int bii
,sel
,pr,pr1
,pr2
,xx,p0,p1
,p2
,pp,pp1
,pp2
,ppp
;
void clear()
{
fo(i,1,tt) b[i]=next[i]=0;
fo(i,1,n) fa[i]=first[i]=dis[i]=bii[i]=sel[i]=pr1[i]=pr2[i]=pd[i]=0;
pp=pr=0;
tmp.c=0;tmp.cp=0;
tp=tp1=ans=tmp;
tt=0;
fo(i,1,n) g[i][0]=g[i][1]=tmp;
}
int read()
{
int x=0;
char ch=getchar();
while (ch<'0'||ch>'9') ch=getchar();
while (ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x;
}
void cr(int x,int y)
{
tt++;
b[tt]=y;
next[tt]=first[x];
first[x]=tt;
}
rec max(rec a,rec b)
{
if (a.c>b.c||(a.c==b.c&&a.cp>b.cp)) return a;return b;
}
rec operator +(rec a,rec b)
{
a.c+=b.c;
a.cp+=b.cp;
return a;
}
bool operator <(rec a,rec b)
{
return a.c<b.c||(a.c==b.c&&a.cp<b.cp);
}
void bfs(int xx)
{
q1=0;
q2=1;
dl[1]=xx;
dis[xx]=1;
while (q1<q2)
{
q1++;
for(p=first[dl[q1]];p;p=next[p])
if (!dis[b[p]])
{
pd[(p+1)/2]=1;
dl[++q2]=b[p];
dis[b[p]]=dis[dl[q1]]+1;
fa[b[p]]=dl[q1];
}else
if (!pd[(p+1)/2])
{
pd[(p+1)/2]=1;
e[1]=dl[q1];d[1]=b[p];
}
}
D=E=1;
if (dis[d[D]]<dis[e[E]])
{
k=d[1];
d[1]=e[1];
e[1]=k;
}
while (dis[d[D]]>dis[e[1]]&&fa[d[D]]!=e[1])
{
d[D+1]=fa[d[D]];
D++;
}
if (dis[d[D]]==dis[e[1]])
{
while (fa[d[D]]!=fa[e[E]])
{
d[D+1]=fa[d[D]];
e[E+1]=fa[e[E]];
D++;E++;
}
d[D+1]=fa[d[D]];D++;
}
for(;E;E--) d[++D]=e[E];
}
void bf(int xx)
{
w1=0;
w2=1;
dd[1]=xx;
ppp[xx]=1;
while(w1<w2)
for(++w1,p=first[dd[w1]];p;p=next[p])
if (!ppp[b[p]]&&(p-1)/2!=ban)
ppp[b[p]]=1,dd[++w2]=b[p],dad[b[p]]=dd[w1];
}
void dp()
{
for(i=w2;i;i--)
{
x=dd[i];
for(p=first[x];p;p=next[p])
if (b[p]!=dad[x]&&(p-1)/2!=ban)
{
tmp.c=1,tmp.cp=0;
if (g[b[p]][1]<g[b[p]][0])
bii[b[p]]=0;
else bii[b[p]]=1;
tp=g[b[p]][bii[b[p]]];
if (sex[x]!=sex[b[p]]) tmp.cp++;
a2=g[x][1]+tp;
b2=g[x][0]+g[b[p]][0]+tmp;
if (a2<b2)
{
if (g[x][1]<b2)
g[x][1]=b2,sel[x]=p;
}
else
g[x][1]=a2;
g[x][0]=g[x][0]+tp;
}
}
x=dd[1];
if (g[x][0]<g[x][1]) sig[x]=1;else sig[x]=0;
dur=g[x][sig[x]];
fo(i,1,w2)
{
x=dd[i];
for(p=first[x];p;p=next[p])
if (b[p]!=dad[x]&&(p-1)/2!=ban)
{
if (sig[x]&&sel[x]==p)
{
pr++;
pr1[pr]=x;pr2[pr]=b[p];
sig[b[p]]=0;
}else sig[b[p]]=bii[b[p]];
}
}
}
int main()
{
freopen("desk.in","r",stdin);
freopen("desk.out","w",stdout);
T=read();
while (T--)
{
clear();
n=read();
fo(i,1,n)
{
a[i]=read();sex[i]=read();
cr(i,a[i]);
cr(a[i],i);
}
fo(xx,1,n)
if (!dis[xx])
{
bfs(xx);
p0=0;pr=0;
for(p=first[d[2]];p;p=next[p])
if (b[p]==d[1])
{
ban=(p-1)/2;
break;
}
dur.c=dur.cp=0;
fo(i,1,w2) g[dd[i]][0]=g[dd[i]][1]=dur,ppp[dd[i]]=0;
bf(d[2]);
dp();
for(p0=pr,tp1=dur;pr;pr--) p1[pr]=pr1[pr],p2[pr]=pr2[pr];
if (D>2)
{
pr=0;
dur.c=dur.cp=0;
fo(i,1,w2) g[dd[i]][0]=g[dd[i]][1]=dur,ppp[dd[i]]=0;
for(p=first[d[2]];p;p=next[p])
if (b[p]==d[3])
{
ban=(p-1)/2;
break;
}
bf(d[2]);
dp();
if (tp1<dur)
for(p0=pr,tp1=dur;pr;pr--) p1[pr]=pr1[pr],p2[pr]=pr2[pr];
}
ans=ans+tp1;
for(;p0;p0--) pp1[++pp]=p1[p0],pp2[pp]=p2[p0];
}
printf("%d %d\n",ans.c,ans.cp);
fo(i,1,pp)
printf("%d %d\n",pp1[i],pp2[i]);
}
}


反思

这道题写了7h吧···问题很多。

1,比赛的时候,没有进一步分析环中多余的边的用处,就直接套多一个DP来处理环,结果写了3h,求方案的地方还错了。在编程太复杂的时候,要多分析问题,找出性质以降低编程复杂度。

2,对于多组数据,数组清零的时候一定要仔细,一清错就要调好久看不出来。清零要适度,不然主程序打的丑就TLE了···

3,注意DFS会爆栈。

4,编程复杂度大的题目,最好在别的地方做好清晰规划,不然脑子记不住。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: