大家好,我是题解搬运工
http://blog.csdn.net/u014609452/article/details/54020829
https://post.icpc-camp.org/d/324-codeforces-round-334-e-pastoral-oddities
介于题解较少,且对于我来说比较难理解,于是决定自己重新写一遍题解
题目大意,选前i条边中的一些边,要求每个点的度都为奇数,且最长边最短。
这道题真是非常难想,首先我们考虑静态的做法。先给出结论,按照边的长短从小到大加入,直到每个联通块的大小都为偶数为止。
如果一个联通块的节点的个数为偶数,那么我们一定可以在联通块中删除一些边,使得其中每个点的度数都为奇数。(我们从叶子节点开始,逐一排查,不行就删边,反正这个结论挺难想的QAQ)
知道这个结论我们就好办了,题目变为用尽量少的边使得每个联通块点数为奇数。下面根据第一个题解,有3种做法
1.LCT
LCT 维护子树异或和与最大值(经典题)
每次操作,考虑第i跳边,x,y,如果xy不连通,则连通,否则看它们的路径上最大的边的权值和当前v的大小,若大于,则删边,加边
在每次加边删边的时候,通过子树大小异或和动态维护此时状态,比较经典的LCT题吧
1 //start: finish: 2 #include<bits/stdc++.h> 3 #define maxn 400005 4 using namespace std; 5 int n,m; 6 int odd; 7 bool rev[maxn]; 8 int c[maxn][2],fa[maxn]; 9 struct node{ 10 int mx,val; 11 int sub,all,zhi; 12 }a[maxn]; 13 pair <int,int> bian[maxn]; 14 set <pair<int,int> > s; 15 int st[maxn]; 16 bool isroot(int x){ 17 return (c[fa[x]][0]!=x && c[fa[x]][1]!=x); 18 } 19 int MAX(int x,int y){ 20 if(!x || !y)return x+y; 21 if(a[x].val>a[y].val)return x; 22 return y; 23 } 24 void update(int x){ 25 a[x].all=a[x].sub^a[x].zhi; 26 if(c[x][0])a[x].all^=a[c[x][0]].all; 27 if(c[x][1])a[x].all^=a[c[x][1]].all; 28 a[x].mx=x; 29 a[x].mx=MAX(a[x].mx,a[c[x][0]].mx); 30 a[x].mx=MAX(a[x].mx,a[c[x][1]].mx); 31 } 32 void down(int x){ 33 if(rev[x]){ 34 rev[c[x][0]]^=1; 35 rev[c[x][1]]^=1; 36 rev[x]^=1; 37 swap(c[x][0],c[x][1]); 38 } 39 } 40 void rotate(int x){ 41 int y=fa[x],z=fa[y]; 42 int l,r; 43 if(c[y][0]==x)l=0;else l=1; 44 r=1-l; 45 if(!isroot(y)){ 46 if(c[z][0]==y)c[z][0]=x; 47 else c[z][1]=x; 48 } 49 fa[x]=z;fa[y]=x;fa[c[x][r]]=y; 50 c[y][l]=c[x][r];c[x][r]=y; 51 update(y);update(x); 52 } 53 void splay(int x){ 54 int top=0;st[++top]=x; 55 for(int i=x;!isroot(i);i=fa[i])st[++top]=fa[i]; 56 for(int i=top;i;i--)down(st[i]); 57 while(!isroot(x)){ 58 int y=fa[x],z=fa[y]; 59 if(!isroot(y)){ 60 if(c[y][0]==x^c[z][0]==y)rotate(x); 61 else rotate(y); 62 } 63 rotate(x); 64 } 65 } 66 void access(int x){ 67 int t=0; 68 while(x){ 69 splay(x); 70 down(x); 71 if(t)a[x].sub^=a[t].all; 72 if(c[x][1])a[x].sub^=a[c[x][1]].all; 73 c[x][1]=t; 74 update(x); 75 t=x; 76 x=fa[x]; 77 } 78 } 79 void move_to_root(int x){ 80 access(x);splay(x);rev[x]^=1; 81 } 82 int find(int x){ 83 access(x);splay(x); 84 while(c[x][0])x=c[x][0]; 85 return x; 86 } 87 void split(int x,int y) 88 { 89 move_to_root(y); 90 access(x); 91 splay(x); 92 } 93 void link(int x,int y){ 94 move_to_root(x); 95 move_to_root(y); 96 fa[x]=y;a[y].sub^=a[x].all; 97 update(y); 98 } 99 void cut(int x,int y){ 100 move_to_root(x); 101 access(y);splay(y); 102 c[y][0]=0;fa[x]=0; 103 update(y); 104 } 105 void Link(int o){ 106 int x=bian[o].first,y=bian[o].second; 107 move_to_root(x);move_to_root(y); 108 if(a[x].all && a[y].all)odd-=2; 109 link(x,o+n); 110 link(y,o+n); 111 } 112 void Cut(int o){ 113 int x=bian[o].first,y=bian[o].second; 114 cut(x,o+n);cut(y,o+n); 115 } 116 bool shan(int o){ 117 int x=bian[o].first,y=bian[o].second; 118 cut(x,o+n);cut(y,o+n); 119 move_to_root(x);move_to_root(y); 120 if(!a[x].all && !a[y].all)return true; 121 link(x,o+n);link(y,o+n); 122 return false; 123 } 124 int main(){ 125 scanf("%d%d",&n,&m); 126 odd=n; 127 for(int i=1;i<=n;i++){ 128 a[i].all=a[i].zhi=1; 129 } 130 for(int i=1;i<=m;i++){ 131 int x,y,z; 132 scanf("%d%d%d",&x,&y,&z); 133 a[i+n].val=z;a[i+n].mx=i+n; 134 bian[i]=make_pair(x,y); 135 if(find(x)!=find(y)){ 136 Link(i); 137 s.insert(make_pair(-z,i)); 138 } 139 else{ 140 split(x,y);int e=a[x].mx; 141 if(a[e].val>z){ 142 Cut(e-n); 143 link(i+n,x); 144 link(i+n,y); 145 s.erase(make_pair(-a[e].val,e-n)); 146 s.insert(make_pair(-z,i)); 147 } 148 } 149 if(odd==0){ 150 while(true){ 151 int e=(*s.begin()).second; 152 if(shan(e)){ 153 s.erase(s.begin()); 154 } 155 else break; 156 } 157 } 158 if(odd)puts("-1"); 159 else printf("%d\n",-(*s.begin()).first); 160 } 161 return 0; 162 }
2.CDQ分治
solve(l,r,ll,rr)代表处理(l,r)的询问,且答案在(ll,rr)之内的情况,然后分治递归,可持久化堆。每次算出mid=(l+r)/2的答案nmid
那么(l,mid-1)的答案在(nmid,r)之间,(mid+1,r)的答案在(l,nmid)之间,递归下去即可
//start: finish: #include<bits/stdc++.h> #define maxn 300005 using namespace std; int n,m; int ans[maxn]; struct DUI { int last,tot; int fa[maxn],sz[maxn]; int p[maxn],q[maxn]; int odd; int find(int x){ if(fa[x]==x)return x; return find(fa[x]); } void bei(){ last=tot; } void merge(int x,int y){ if(find(x)==find(y))return; x=find(x);y=find(y); if(sz[x]%2==1 && sz[y]%2==1)odd-=2; if(sz[x]>sz[y])swap(x,y); fa[x]=y;sz[y]+=sz[x]; p[++tot]=x;q[tot]=y; } void back(int mu) { while(tot>mu){ int x=p[tot],y=q[tot]; fa[x]=x;sz[y]-=sz[x]; if(sz[x]%2==1 && sz[y]%2==1)odd+=2; tot--; } } }D; struct node { int x,y,z; }a[maxn]; int p[maxn],rnk[maxn]; bool cmp(int x,int y) { return a[x].z<a[y].z; } void solve(int l,int r,int ll,int rr){ if(l>r)return; int tmp=D.tot; //cout<<l<<" "<<r<<" "<<a[p[ll]].z<<" "<<a[p[rr]].z<<" "<<D.tot<<endl; int mid=(l+r)/2; for(int i=l;i<=mid;i++)if(rnk[i]<ll)D.merge(a[i].x,a[i].y); int nmid=-1; for(int i=ll;i<=rr && D.odd;i++){ if(p[i]<=mid){ D.merge(a[p[i]].x,a[p[i]].y); } if(D.odd==0){ nmid=i; break; } } D.back(tmp); if(nmid==-1){ for(int i=l;i<=mid;i++)ans[i]=-1; for(int i=l;i<=mid;i++)if(rnk[i]<ll)D.merge(a[i].x,a[i].y); solve(mid+1,r,ll,rr); D.back(tmp); return; } ans[mid]=a[p[nmid]].z; for(int i=ll;i<nmid;i++){ if(p[i]<l)D.merge(a[p[i]].x,a[p[i]].y); } solve(l,mid-1,nmid,rr); D.back(tmp); for(int i=l;i<=mid;i++)if(rnk[i]<ll)D.merge(a[i].x,a[i].y); solve(mid+1,r,ll,nmid); D.back(tmp); } int main() { // freopen("1.in","r",stdin); // freopen("1.out","w",stdout); scanf("%d%d",&n,&m); D.odd=n; for(int i=1;i<=n;i++)D.fa[i]=i,D.sz[i]=1; for(int i=1;i<=m;i++) { scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z); p[i]=i; } sort(p+1,p+m+1,cmp); for(int i=1;i<=m;i++)rnk[p[i]]=i; solve(1,m,1,m); for(int i=1;i<=m;i++) { printf("%d\n",ans[i]); } return 0; } /* 4 10 2 1 999999029 3 1 999999800 4 2 999999466 1 3 9961 1 3 9960 2 4 9943 3 1 9938 1 3 9923 4 2 9917 3 4 9903 3 4 9890 4 1 9884 1 3 9880 2 1 9862 1 2 9856 */
3.线段树做法
通过上一个方法,就可以容易发现,如果一条边在第i时间在答案中,那么它从出生开始到i都在答案中,于是用线段树维护每个时间有哪些边在,然后更新答案,线段树用来存储这一段区间里必有哪些边,依然需要可持久化堆。
//start: finish: #include<bits/stdc++.h> #define maxn 1600005 using namespace std; int n,m; int ans[maxn]; struct node{ int x,y,z; }a[maxn]; int p[maxn],now; vector <int> v[maxn]; bool cmp(int x,int y) { return a[x].z<a[y].z; } struct DUI { int last,tot; int fa[maxn],sz[maxn]; int p[maxn],q[maxn]; int odd; int find(int x){ if(fa[x]==x)return x; return find(fa[x]); } void bei(){ last=tot; } void merge(int x,int y){ if(find(x)==find(y))return; x=find(x);y=find(y); if(sz[x]%2==1 && sz[y]%2==1)odd-=2; if(sz[x]>sz[y])swap(x,y); fa[x]=y;sz[y]+=sz[x]; p[++tot]=x;q[tot]=y; } void back(int mu) { while(tot>mu){ int x=p[tot],y=q[tot]; fa[x]=x;sz[y]-=sz[x]; if(sz[x]%2==1 && sz[y]%2==1)odd+=2; tot--; } } }D; void modify(int x,int l,int r,int tx,int ty,int wh){ if(l>=tx && r<=ty){ v[x].push_back(wh); return; } int mid=(l+r)/2; if(mid>=tx)modify(x*2,l,mid,tx,ty,wh); if(mid<ty)modify(x*2+1,mid+1,r,tx,ty,wh); } void solve(int x,int l,int r){ int tmp=D.tot; // cout<<x<<" "<<l<<" "<<r<<" "<<D.odd<<" "<<v[x].size()<<endl; for(int i=0;i<(int)v[x].size();i++)D.merge(a[v[x][i]].x,a[v[x][i]].y); if(l!=r){ int mid=(l+r)/2; solve(x*2+1,mid+1,r); solve(x*2,l,mid); } else{ // cout<<D.odd<<endl; for(;now<=m && D.odd;now++){ int id=p[now]; if(id>l)continue; D.merge(a[id].x,a[id].y); if(id<l){ // cout<<id<<" "<<l-1<<" "<<id<<endl; modify(1,1,m,id,l-1,id); } } if(D.odd)ans[l]=-1;else ans[l]=a[p[now-1]].z; } D.back(tmp); } int main(){ scanf("%d%d",&n,&m); D.odd=n; for(int i=1;i<=n;i++){ D.sz[i]=1; D.fa[i]=i; } for(int i=1;i<=m;i++){ scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z); p[i]=i; } sort(p+1,p+m+1,cmp); now=1; solve(1,1,m); for(int i=1;i<=m;i++){ printf("%d\n",ans[i]); } return 0; }
小结:
这三个方法都建立在前面的结论上,这个结论还是很强大的
方法一:
方法二:
方法三:
后面两种方法的代码长度和时间都比第一种短,但第一种最简单直观。
个人还是非常喜欢这道题的。