• 【题解】CF603E Pastoral Oddities (分治)


    【题解】CF603E Pastoral Oddities (分治)

    完全不会做

    考虑下这个奇怪的条件"每个点的度数都是奇数"。但这个图是一个无向图,点的总度数应该是偶数。因此对于任何一个联通块,这个联通块合法的必要条件是点的个数是偶数个,否则度数都不一样。

    然而这个同时是一个必要条件,这里给出一个构造,你弄一颗dfs树出来,不妨设这些点为$1sim n$,$1$是根。按照dfs序反序考虑每个点,若一个点的度数是偶数则断开父边,否则保留这条父边。通过这样的算法我们可以直接钦定除了1的所有点的度数是奇数,由于断开一条边不改变总度数奇偶性(总度数变化量为2),那么1的度数$equiv 2-(n-1)equiv n+1pmod 2$是奇数。

    再考虑联通块之间的关系,发现两个偶联通块连不连一起无所谓,不影响答案; 两个奇联通块一定连一起这样消掉了一个;一奇一偶连在一起也无所谓。因此有边我们就连最优,而答案要求我们最大的边最小,那么这相当于做最小生成树的过程。可以lct动态维护最小生成树,也可以按时间分治。

    考虑$ m Kruskal$算法的过程,我们要从大到小枚举每条边,而且我们还要保证假如的边的时间在前面。考虑这样一种分治结构:

    ({ m solve}(l,r,L,R)), 表示我要考虑$[l,r](时间中的答案,且这个答案范围为)[L,R]$,且并查集已经囊括了所有$w<L$且在时间$[l,r](中的边。我们将)[l, m mid ](中的边按边权依次加入得到) m ans[mid]$。

    此外我们发现$ m ans[]$ 是单调递减的,因此我们可以确定$[l, m mid)(的答案)ge m ans[mid](,而)(mid,r](的答案)le m ans[mid]$。也就是说:

    • 对于$(mid,r](中的答案,所有)> ans[ m mid]$的边都可以忽略;

    • 对于$[l, m mid )(的答案,)le m ans[mid]$的所有边都会被加入并查集中。(这两个结论都是根据最小生成树的性质得到)

    这也就意味着,一条边在并查集中产生对$ m ans[x]$的时间是一段连续的。

    那么在递归进入这两边子问题之前,把 可以忽略/必定加入 的边先加入进来再递归。这样一条边至多会被$O(log n)(次求解) m ans[mid]$的过程中被加入。(一条边存在是一个区间,我们相当于把这个区间拆分成了$O(log n)$个区间)

    复杂度$O(mlog m log n )$

    //@winlere
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    #include<map>
    
    using namespace std;  typedef long long ll;
    inline int qr(){
    	int ret=0,f=0,c=getchar();
    	while(!isdigit(c)) f|=c==45,c=getchar();
    	while( isdigit(c)) ret=ret*10+c-48,c=getchar();
    	return f?-ret:ret;
    }
    const int maxn=1e5+5;
    const int inf=0x3f3f3f3f;
    int r[maxn],siz[maxn],stk[maxn],top,n,m,cnt_odd;
    int Find(int x){return x==r[x]?x:Find(r[x]);}
    int Merge(int x,int y){
    	x=Find(x),y=Find(y);
    	if(x==y) return 0;
    	if(siz[x]>siz[y]) swap(x,y);
    	cnt_odd-=siz[x]&1; cnt_odd-=siz[y]&1;
    	r[x]=y; siz[y]+=siz[x]; stk[++top]=x;
    	cnt_odd+=siz[y]&1;
    	return 1;
    }
    void Undo(int x=1e8){
    	while(x--){
    		int u=stk[top],v=r[u];
    		cnt_odd-=siz[v]&1;
    		siz[v]-=siz[u];
    		cnt_odd+=siz[v]&1; cnt_odd+=siz[u]&1;
    		r[u]=u;
    		top--;
    	}
    }
    
    int ans[maxn*3];
    struct E{int u,v,w;}e[maxn*3];
    map<int,vector<int>> C;
    
    #define mid ((l+r)>>1)
    void solve(int l,int r,int L,int R){
    	if(L>R) return;
    	int base=0,sav=0,sav1=0,ret=R;
    	for(int t=l;t<=mid;++t)
    		if(e[t].w<L) base+=Merge(e[t].u,e[t].v);
    	for(auto it=C.lower_bound(L),ed=C.upper_bound(R);it!=ed;++it)
    		for(auto t:it->second)
    			if(t<=mid){
    				if( cnt_odd) sav+=Merge(e[t].u,e[t].v);
    				if(!cnt_odd) {ret=e[t].w;it=prev(ed);break;}
    			}
    	ans[mid]=ret; Undo(sav);
    	if(mid<r) solve(mid+1,r,L,ret);
    	Undo(base);
    	for(auto it=C.lower_bound(L),ed=C.lower_bound(ret);it!=ed;++it)
    		for(auto t:it->second)
    			if(t<l) sav1+=Merge(e[t].u,e[t].v);
    	if(l<mid) solve(l,mid-1,ret,R);
    	Undo(sav1);
    }
    #undef mid
    
    int main(){
    	memset(ans,0x3f,sizeof ans);
    	n=qr(),m=qr();
    	for(int t=1;t<=n;++t) r[t]=t,siz[t]=1;
    	cnt_odd=n;
    	for(int t=1;t<=m;++t) e[t].u=qr(),e[t].v=qr(),e[t].w=qr(),C[e[t].w].push_back(t);
    	solve(1,m,1,inf);
    	for(int t=1;t<=m;++t) printf("%d
    ",ans[t]==inf?-1:ans[t]);
    	return 0;
    }
    
    
    
  • 相关阅读:
    php 为什么new一个对象后面要加一个反斜杠
    c# 判断当前时间是否在某一时间段内
    关于Entity Framework的概念及搭建
    mvc 读写txt文档
    winform :DataGridView添加一列checkbox
    使用filter进行登录验证,并解决多次重定向问题
    关于Select选中问题
    错误:Parameter '0' not found.Available parameters are [arg1, arg0, param1, param2]的解决方法
    sql-省市区
    设置oracle主键自增长
  • 原文地址:https://www.cnblogs.com/winlere/p/13026951.html
Copyright © 2020-2023  润新知