• 6491. 【GDOI2020模拟03.04】铺路


    题目

    有一个图,每次加入一条边,对于每个加入的边分别输出加入这条边之后,加入的所有边中选择若干条使得所有点的度数为奇数,用到的边最长是多少。


    思考历程

    比赛时是推出了很多条性质的。
    但是一开始就理解错了题意,题目中的“道路”我理解成了路径,实际上是边。
    由于经过这种想法改过的题意过于毒瘤,我几乎没有思路。
    最终打了暴力上去,居然能水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;
    }
    

    总结

    如果比赛时看懂了题意,说不定当时就想出来了呢……

  • 相关阅读:
    Python运维开发之路《WEB框架:Django》
    Python运维开发之路《函数进阶》
    Python运维开发之路《编程》
    Python运维开发之路《模块》
    Linux系统运维之Web服务器Nginx安装
    Python运维开发之路《高阶函数》
    Python运维开发之路《函数》
    <读书笔记>软件调试之道 :实证方法
    (知识分享)软硬件调试九法:第九条规则 如果你不修复一个bug,它将永远 存在
    (知识分享)软硬件调试九法:第八条规则 求助他人, 获得全新观点
  • 原文地址:https://www.cnblogs.com/jz-597/p/12459317.html
Copyright © 2020-2023  润新知