• 【CF603E】Pastoral Oddities


    题目

    经过大力手玩不难发现存在一个边集使得每个点的度数都是奇数的充要条件是不存在奇数个点的联通块

    考虑证明奇数个点的联通块一定不合法。刚开始所有点度数均为偶数,考虑加入一条边((u,v))的影响:

    • (u,v)度数均为偶数,那么加入((u,v))后两点度数均变为奇数,奇度点个数增加(2)

    • (u,v)度数均为奇数,那么加入((u,v))后两点度数均变为偶数,奇度点个数减少(2)

    • (u,v)度数一奇一偶,那么加入((u,v))之后奇偶性交换,奇度点个数不变。

    不难发现加入一条边会令奇度点个数(+2,-2)或不变,于是奇度点个数始终为偶数,不可能存在一个奇数个点的联通块满足条件。

    对于偶数个点的联通块,可以直接找出其一棵生成树,之后用一层层删叶子的方法就能构造出一个合法的边集。

    现在的我们需要求出一个边集,使得所有联通块均有偶数个点,且最小化最大边集。不难想到一个类似于kruskal的做法,即将所有边从小到大排序,并查集维护联通块的点数,顺次加入每一条边,直到不存在奇数个点的联通块为止。

    考虑到加入边的过程中答案是不升的,即加入一条边后答案不可能变大,于是考虑用cdq分治来解决问题。

    设当前我们处理的是([l,r])内的边,且答案在([x,y])中;我们保证,加入时间在([1,l))且权值小于(x)的都已经被加入了。尝试求出(ans_{mid}),这样就能分治到([l,mid-1,ans_{mid},y])([mid+1,r,x,ans_{mid}])去处理。

    我们先将([l,mid])中边权小于(x)的边加入,之后在从小到大加入权值在([x,y])之间的边;一旦满足条件就停止,此时最后加入的边的边权就是(ans_{mid})

    当往下分治的时候,我们需要保证性质继续满足;于是分治左区间的时候需要把权值小于(ans_{mid})的边中出现时间小于(l)的边加入,分治右区间的时候需要把出现时间小于(mid+1)的边中权值小于(x)的加入。

    由于分治完后要撤回新加入的边,于是可以维护一个支持撤回的并查集,时间复杂度是(O(mlog mlog n))

    代码

    #include<bits/stdc++.h>
    #define re register
    inline int read() {
    	char c=getchar();int x=0;while(c<'0'||c>'9')c=getchar();
    	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
    }
    const int maxn=1e5+5,maxm=3e5+5;
    struct Edge{int u,v,t,w;}e[maxm],E[maxm];
    int n,m,ans[maxm],id[maxm];
    inline int cmp(const Edge &A,const Edge &B) {return A.w<B.w;}
    struct dsu{
    	int fa[maxn],sz[maxn],tot,top,st[maxn];
    	inline void bud() {
    		tot=n;for(re int i=1;i<=n;i++)fa[i]=i,sz[i]=1;
    	}
    	inline int find(int x) {
    		while(x!=fa[x])x=fa[x];return x;
    	}
    	inline void lnk(int x,int y) {
    		x=find(x),y=find(y);if(x==y) return;
    		if(sz[x]<sz[y]) x^=y,y^=x,x^=y;
    		fa[y]=x;tot-=(sz[x]&1),tot-=(sz[y]&1);
    		sz[x]+=sz[y];tot+=(sz[x]&1);st[++top]=y;
    	}
    	inline void back() {
    		int y=st[top--],x=fa[y];
    		tot-=(sz[x]&1);sz[x]-=sz[y];fa[y]=y;
    		tot+=(sz[x]&1);tot+=(sz[y]&1);
    	}
    }S;
    void cdq(int l,int r,int x,int y) {
    	if(l>r) return;
    	int mid=l+r>>1,pre=S.top;
    	for(re int i=l;i<=mid;++i)
    		if(id[i]<x) S.lnk(E[i].u,E[i].v);
    	for(re int i=x;i<=y;++i) {
    		if(e[i].t<=mid) S.lnk(e[i].u,e[i].v); 
    		if(!S.tot) {ans[mid]=i;break;}
    	}
    	while(S.top>pre) S.back();
    	if(!ans[mid]) {
    		for(re int i=l;i<=mid;++i) 
    			if(id[i]<x) S.lnk(E[i].u,E[i].v);
    		cdq(mid+1,r,x,y);
    		while(S.top>pre) S.back();
    		return;	
    	}
    	for(re int i=l;i<=mid;++i) 
    		if(id[i]<x) S.lnk(E[i].u,E[i].v);
    	cdq(mid+1,r,x,ans[mid]);
    	while(S.top>pre) S.back();
    	for(re int i=x;i<ans[mid];++i) 
    		if(e[i].t<l) S.lnk(e[i].u,e[i].v);
    	cdq(l,mid-1,ans[mid],y);
    	while(S.top>pre) S.back();
    	
    }
    int main() {
    	n=read(),m=read();S.bud();
    	for(re int i=1;i<=m;i++)
    		e[i].t=i,e[i].u=read(),e[i].v=read(),e[i].w=read(); 
    	for(re int i=1;i<=m;i++)E[i]=e[i];
    	std::sort(e+1,e+m+1,cmp);
    	for(re int i=1;i<=m;i++)id[e[i].t]=i;cdq(1,m,1,m);
    	for(re int i=1;i<=m;++i)if(!ans[i]) puts("-1");else printf("%d
    ",e[ans[i]].w);
    	return 0;
    }
    
  • 相关阅读:
    变量在函数内外的作用域 3
    php中用大括号把?>和<?php框起来的作用
    变量在函数内外的作用域 2
    变量在函数内外的作用域
    字母大小写对变量和函数的区别
    require()和include()代码重用
    str_place()替换函数
    【开源框架】Android之史上最全最简单最有用的第三方开源库收集整理,有助于快速开发,欢迎各位网友补充完善
    android SQLite使用SQLiteOpenHelper类对数据库进行操作
    tomcat设置IP地址或者域名访问
  • 原文地址:https://www.cnblogs.com/asuldb/p/12887439.html
Copyright © 2020-2023  润新知