Educational Codeforces Round 112 部分题题解

E.Boring Segments

题目链接

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

题目链接

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");
    }
}
posted @ 2021-07-31 11:44  Alkaid~  阅读(78)  评论(0编辑  收藏  举报