您的位置:首页 > 产品设计 > UI/UE

[agc14e]Blue and Red Tree

2017-09-27 22:13 453 查看

前言

这题主要是要想到倒过来,是一个很唯一的过程。

我大概想到了,但我比题解蠢。

题解的不需要区分红蓝让我觉得很机智。

题意

有一颗全是蓝边的树,你对其执行n-1次操作。

每次操作选择一条全是蓝边的路径,将其中的一条蓝边断开,假设选择的路径是j->k,断的边是x-y,且断开后j和x联通,那么k和y联通。

然后你需要选择j->x上的一个节点,以及k->y上的一个节点,在这两个节点间连红边。

现在问你能否存在方案变着指定的全是红边的树。

题解

考虑倒过来加边。

这个过程是很唯一的。

首先一开始有很多单点联通块。

每次如果两个联通块之间存在蓝边与红边相连,合并两个联通块。

最后合并成一个就行了。

好实现的方法是,这个红和蓝我们可以认为没有区别。

于是可以用一个queue存需要合并的联通块对(即之间存在两条边),用map来存两个联通块间的边数,用set存与某个联通块存在边的联通块,用并查集来维护连通性,合并两个联通块只需要启发式合并即可。

这样真好写!

#include<cstdio>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10;
typedef pair<int,int> pi;
pi zlt;
map<pi,int> cnt,bz;
set<int> s[maxn];
set<int>::iterator it;
queue<pi> dl;
int fa[maxn];
int i,j,k,l,t,n,m,x,y,z,ca;
bool czy;
int getfa(int x){
return fa[x]?fa[x]=getfa(fa[x]):x;
}
int main(){
scanf("%d",&n);
fo(i,1,2*(n-1)){
scanf("%d%d",&j,&k);
if (j>k) swap(j,k);
zlt=make_pair(j,k);
cnt[zlt]++;
if (cnt[zlt]==1){
s[j].insert(k);
s[k].insert(j);
}
if (cnt[zlt]>1&&!bz[zlt]){
dl.push(zlt);
bz[zlt]=1;
}
}
czy=1;
ca=n-1;
while (ca--){
x=y=-1;
while (!dl.empty()){
j=getfa(dl.front().first);
k=getfa(dl.front().second);
dl.pop();
if (j!=k){
x=j;y=k;
break;
}
}
if (x==y&&x==-1){
czy=0;
break;
}
if (s[x].size()<s[y].size()) swap(x,y);
fa[y]=x;
it=s[y].begin();
while (it!=s[y].end()){
z=*it;
it++;
s[z].erase(s[z].find(y));
if (z==x) continue;
//if (s[x].find(z)==s[x].end()) s[x].insert(z),s[z].insert(x);
if (x<z) zlt=make_pair(x,z);else zlt=make_pair(z,x);
cnt[zlt]++;
if (cnt[zlt]==1){
s[x].insert(z);
s[z].insert(x);
}
if (cnt[zlt]>1&&!bz[zlt]){
dl.push(zlt);
bz[zlt]=1;
}
}
s[y].clear();
}
if (czy) printf("YES\n");else printf("NO\n");
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: