题目
有一个图,每次加入一条边,对于每个加入的边分别输出加入这条边之后,加入的所有边中选择若干条使得所有点的度数为奇数,用到的边最长是多少。
思考历程
比赛时是推出了很多条性质的。
但是一开始就理解错了题意,题目中的“道路”我理解成了路径,实际上是边。
由于经过这种想法改过的题意过于毒瘤,我几乎没有思路。
最终打了暴力上去,居然能水5分。
正解
有条性质:
一个连通块符合条件,当且仅当这个连通块的总点数为偶数。
(这个连通块是只要用的和不用的边都加进去形成的连通块)
首先,通过度数的奇偶性,可以发现把用到的边都加进去之后,最终形成的都是偶数连通块。
考虑在偶数联通块中构造一个合法的方案:建立一棵生成树,随便选一个点作为根,从叶子往上做。首先选择叶子的父亲边,保证叶子的度数为奇数。对于上面的每个点,看看有多少个通向儿子的边被选了,根据它的奇偶性来决定它到父亲的边选不选。
最终只剩下根节点。我们可以通过度数总和来计算出根节点的度数,发现这时候它也是奇数度数的。
这样就证完了。
这条性质是这道题的灵魂,有了它,意味着我们需要选择一些边,使得图分成若干个偶数连通块。这些边可能在最终的答案中不会用到,但没有关系。这时候答案为最长边。
可以这样暴力:直接最小生成树,维护奇数连通块的个数,第一次等于零的时候输出最后加入的那条边。
由此延伸出了无数种做法,比如可以随便LCT撵爆。
讲个线段树分治。
对于一条边,考虑它在那些询问中被选择,容易发现这是一段区间,头是它加入的时候,我们试着计算出尾。
用一个链表从小到大存下所有边,表示没有确定出尾的位置的边有哪些。
线段树分治的时候从后往前做。到叶子节点的时候,如果当前仍然存在奇数区间,那就将链表中的边从头到尾扔进图中,直到不存在奇数区间为止。这些边在这个时候已经确定了它们的尾,所以把他们从并查集中删去,并在它们影响到的区间上打上标记。
进入一个节点时将标记的边加入图中,退出的时候撤销。
代码
using namespace std;
#include <cstdio>
#include <cstring>
#include <cassert>
#include <algorithm>
#define N 100010
#define M 300010
int n,m;
struct edge{
int u,v,w;
} ed[M];
int q[M],nxt[M];
bool cmpq(int a,int b){return ed[a].w<ed[b].w;}
struct EDGE{
int id;
EDGE *las;
} e[M*40];
int ne;
EDGE *last[M*4];
int odd;
int fa[N],siz[N];
int getfa(int x){return fa[x]==x?x:getfa(fa[x]);}
int opt[N],cnt;
void clear(int btm){
for (;cnt>btm;--cnt){
int u=opt[cnt],v=fa[u];
odd=odd-(siz[v]&1)+(siz[u]&1)+(siz[v]-siz[u]&1);
siz[v]-=siz[u];
fa[u]=u;
}
}
int ans[M];
void cover(int k,int l,int r,int st,int en,int x){
if (st<=l && r<=en){
e[ne]={x,last[k]};
last[k]=e+ne++;
return;
}
int mid=l+r>>1;
if (st<=mid)
cover(k<<1,l,mid,st,en,x);
if (mid<en)
cover(k<<1|1,mid+1,r,st,en,x);
}
void calc(int k,int mx){
int tmp=cnt,x=nxt[0],lst=0;
for (;odd && x<=m;lst=x,x=nxt[x]){
nxt[0]=nxt[x];
if (q[x]>k)
continue;
if (q[x]<=k-1)
cover(1,1,m,q[x],k-1,q[x]);
mx=max(mx,ed[q[x]].w);
int u=getfa(ed[q[x]].u),v=getfa(ed[q[x]].v);
if (u!=v){
odd=odd-(siz[u]&1)-(siz[v]&1)+(siz[u]+siz[v]&1);
if (siz[u]>siz[v])
swap(u,v);
fa[u]=v;
siz[v]+=siz[u];
opt[++cnt]=u;
}
}
if (odd)
ans[k]=-1;
else
ans[k]=mx;
clear(tmp);
}
void dfs(int k,int l,int r,int mx){
int tmp=cnt;
// printf("%d %d
",k,last[k]);
for (EDGE *ei=last[k];ei;ei=ei->las){
int u=getfa(ed[ei->id].u),v=getfa(ed[ei->id].v);
mx=max(mx,ed[ei->id].w);
if (u==v)
continue;
odd=odd-(siz[u]&1)-(siz[v]&1)+(siz[u]+siz[v]&1);
if (siz[u]>siz[v])
swap(u,v);
fa[u]=v;
siz[v]+=siz[u];
opt[++cnt]=u;
}
if (l==r){
calc(l,mx);
clear(tmp);
return;
}
int mid=l+r>>1;
dfs(k<<1|1,mid+1,r,mx);
dfs(k<<1,l,mid,mx);
clear(tmp);
}
int main(){
freopen("road.in","r",stdin);
freopen("road.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=m;++i)
scanf("%d%d%d",&ed[i].u,&ed[i].v,&ed[i].w),q[i]=i;
sort(q+1,q+m+1,cmpq);
for (int i=0;i<=m;++i)
nxt[i]=i+1;
odd=n;
for (int i=1;i<=n;++i)
fa[i]=i,siz[i]=1;
dfs(1,1,m,0);
for (int i=1;i<=m;++i)
printf("%d
",ans[i]);
return 0;
}
总结
如果比赛时看懂了题意,说不定当时就想出来了呢……