Educational Codeforces Round 112 部分题题解
E.Boring Segments
题目链接
简要题解
本题要求取出一些线段覆盖整个区间,代价是取出线段中最大权值与最小权值的差。
我们把线段按照权值从小到大排序,那么肯定有一个最优方案,是取出序列中连续的一些线段覆盖整个区间。
于是我们可以枚举最终方案的最小权值,然后按权值从小到大把线段放在区间上,直到覆盖整个区间。
计算当前方案对答案的贡献,然后删去权值最小的线段,若区间未被完全覆盖则继续加入权值更大的线段。
这样的话,每条线段只会被加入一次,删除一次,保证了时间复杂度。
具体实现的话,可以利用线段树,每次加入/删除线段时,就在线段树上对应区间全部\(+1/-1\),并且维护区间最小值。
整个区间被全部覆盖,当且仅当线段树的全局最小值为正。
注意题目中说到,我们需要使线段有交才能转移,但是线段树并不能判交,实际上我们把所有区间的右端点减1,就不需要使区间有交了。
时间复杂度\(O(nlogm)\)
代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3e5+10;
const int MAXM=1e6+10;
const int Inf=1e9;
struct ELE{ int Le,Ri,W; }Seg[MAXN];
int n,m,Ans;
int Read()
{ int a=0,c=1; char b=getchar();
while(b!='-'&&(b<'0'||b>'9')) b=getchar();
if(b=='-') c=-1,b=getchar();
while(b>='0'&&b<='9') a=a*10+b-48,b=getchar();
return a*c;
}
int Min(int A,int B){ return A<B?A:B; }
int Max(int A,int B){ return A>B?A:B; }
bool cmp(ELE A,ELE B){ return A.W<B.W; }
namespace TREE
{ int Mins[MAXM<<2],Lan[MAXM<<2];
void Push_down(int S)
{ if(!Lan[S]) return ;
Mins[S<<1]+=Lan[S],Mins[S<<1|1]+=Lan[S],Lan[S<<1]+=Lan[S],Lan[S<<1|1]+=Lan[S],Lan[S]=0;
}
int Modify(int S,int Le,int Ri,int Al,int Ar,int Num)
{ if(Al<=Le&&Ri<=Ar) return Mins[S]+=Num,Lan[S]+=Num;
int Mid=(Le+Ri)>>1;
Push_down(S);
if(Al<=Mid) Modify(S<<1,Le,Mid,Al,Ar,Num);
if(Mid<Ar) Modify(S<<1|1,Mid+1,Ri,Al,Ar,Num);
return Mins[S]=Min(Mins[S<<1],Mins[S<<1|1]);
}
}using namespace TREE;
int main()
{ n=Read(),m=Read()-1,Ans=Inf;
for(int i=1;i<=n;i++) Seg[i].Le=Read(),Seg[i].Ri=Read()-1,Seg[i].W=Read();
sort(Seg+1,Seg+n+1,cmp);
for(int i=1,Ri=1;i<=n;i++)
{ while(Mins[1]==0&&Ri<=n) Modify(1,1,m,Seg[Ri].Le,Seg[Ri].Ri,1),Ri++;
if(Mins[1]>0) Ans=Min(Ans,Seg[Ri-1].W-Seg[i].W);
if(i<Ri) Modify(1,1,m,Seg[i].Le,Seg[i].Ri,-1);
}
printf("%d\n",Ans);
}
F.Good Graph
题目链接
简要题解
我们先按照正常思路来想,什么时候加边可以成功?什么时候会失败呢?
如果我们加入的这条边不在环上,即两个端点不连通,那么肯定可以成功。
如果我们加入的这条边在环上,那么需要满足两个条件:环的异或和为1,环上所有边不能出现在其他环上。
第二个条件是怎么来的?
如果存在两个环有共同边,这两个环的最外围会形成一个大环。由于它们各自的异或和都为\(1\),那么这个大环的异或和为0。
那么我们需要维护的操作变成了:
如果当前边的两端点不连通,就直接加边,这样整张图始终是一个森林。
如果当前边的两端点连通,我们需要检查两端点树上路径所有边与当前边的异或和是否为1,且检查路径上是否有边在环内。
如果条件都满足,那么我们就“加入”当前边。
这并不需要真的在图上加边,考虑它对后续操作的影响,我们只需要将路径上的所有树边标记为在环内即可。
具体实现方式,这里提供两个思路。
第一种是\(LCT\),可以加边,也可以维护路径信息,但是我们需要对每条树边新建一个点,才能用\(LCT\)维护。
若端点不连通,则新建一个点代表该边,点权设为边权,再把该点与两端点连通。
若端点连通,可以直接查询路径点权异或和,以及标记的或来判断是否合法。
若合法,需要对该路径打上标记,注意只能对代表边的点打标记,这个在懒标记下放的时候需要特判一下。
第二种是离线做法,我们发现,按照我们的操作来进行的话,树边是不会受非树边影响的。
因此我们可以把所有操作离线,按操作顺序尝试加边,只处理两端点不连通的边,这样会形成一颗森林。
我们直接用数据结构维护这个森林,比如树剖,就不需要\(LCT\)了。
图的形态固定下来之后,我们再重新按顺序处理非树边即可。
\(LCT\)做法的代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=6e5+10;
int n,Qs,Ts;
int Read()
{ int a=0,c=1; char b=getchar();
while(b!='-'&&(b<'0'||b>'9')) b=getchar();
if(b=='-') c=-1,b=getchar();
while(b>='0'&&b<='9') a=a*10+b-48,b=getchar();
return a*c;
}
namespace LCT
{ bool Lan[MAXN],Die[MAXN],Plus[MAXN],Val[MAXN],Xor[MAXN],Or[MAXN];
int Top,Son[MAXN][2],Fa[MAXN],Tot[MAXN],Stack[MAXN];
bool Not_Root(int S){ return Son[Fa[S]][0]==S||Son[Fa[S]][1]==S; }
void Push_Lan(int S){ if(S) swap(Son[S][0],Son[S][1]),Lan[S]^=1; }
void Push_Plus(int S){ if(S) Plus[S]=1,Die[S]|=(S>n),Or[S]=Die[S]|(Tot[S]>1); }
void Push_up(int S)
{ Tot[S]=Tot[Son[S][0]]+Tot[Son[S][1]]+1;
Xor[S]=Xor[Son[S][0]]^Xor[Son[S][1]]^Val[S];
Or[S]=Or[Son[S][0]]|Or[Son[S][1]]|Die[S];
}
void Push_down(int S)
{ if(Lan[S]) Push_Lan(Son[S][0]),Push_Lan(Son[S][1]),Lan[S]=0;
if(Plus[S]) Push_Plus(Son[S][0]),Push_Plus(Son[S][1]),Plus[S]=0;
}
void Rotate(int S)
{ int Nf=Fa[S],Gf=Fa[Nf],Tos=Son[Nf][1]==S,Tofa=Son[Gf][1]==Nf;
if(Not_Root(Nf)) Son[Gf][Tofa]=S;
if(Son[S][Tos^1]) Fa[Son[S][Tos^1]]=Nf;
Fa[S]=Gf,Fa[Nf]=S,Son[Nf][Tos]=Son[S][Tos^1],Son[S][Tos^1]=Nf,Push_up(Nf),Push_up(S);
}
void Splay(int S)
{ int Nf=S,Gf=0; Stack[++Top]=S;
while(Not_Root(Nf)) Nf=Fa[Nf],Stack[++Top]=Nf;
while(Top) Push_down(Stack[Top--]);
while(Not_Root(S))
{ Nf=Fa[S],Gf=Fa[Nf];
if(Not_Root(Nf)) (Son[Nf][1]==S)^(Son[Gf][1]==Nf)?Rotate(S):Rotate(Nf);
Rotate(S);
}
}
void Access(int S)
{ for(int i=0;S;S=Fa[i=S])
Splay(S),Son[S][1]=i,Push_up(S);
}
int Find_Root(int S)
{ Access(S),Splay(S);
while(Son[S][0]) S=Son[S][0];
return S;
}
void Make_Root(int S){ Access(S),Splay(S),Push_Lan(S); }
void Split(int A,int B){ Make_Root(A),Access(B),Splay(B); }
void New(int A,int B,int X){ Ts++,Val[Ts]=Xor[Ts]=X,Fa[A]=Ts,Make_Root(B),Fa[B]=Ts; }
int Query(int A,int B,int X)
{ Access(B),Splay(B);
if(Xor[B]^X==1&&Or[B]==0) return Push_Plus(B),1;
return 0;
}
}using namespace LCT;
int main()
{ n=Read(),Qs=Read(),Ts=n;
for(int i=1,A,B,X;i<=Qs;i++)
{ A=Read(),B=Read(),X=Read(),Make_Root(A);
if(Find_Root(B)!=A) New(A,B,X),puts("YES");
else if(Query(A,B,X)) puts("YES");
else puts("NO");
}
}