Codeforces 1774 G Segment Covering 题解 (观察性质,倍增)
为什么这题是要求偶数方案数与奇数方案数的差,而不是直接求总方案数呢?这时候就应该往一个偶方案和一个奇方案抵消的方向去想。对于一个线段i,我们称它是不好的,当且仅当存在线段j使得\(x_i\le x_j\le y_j\le y_i\),也就是它包含一个别的线段;否则称这个线段是好的。我们来观察一个单独的询问[l,r]。首先只有完全包含在(l,r)内的线段才能被选。我们先固定所有被选的不好的线段,令它们的集合为S。再固定所有没被S中任意一条线段包含的好线段中,有哪些被选。剩下还没被决定选不选的线段都是被S中至少一条线段包含的好线段,注意到这种线段至少有一条,也就是此时的奇数方案数与偶数方案数相等。因此,当有不好的线段被选进答案时,不对最终答案产生贡献。所以我们可以直接把所有的不好的线段删掉,是不影响最后答案的。这一步排个序就能做到。
接下来所有的线段就是两两不包含的了。对于一个询问[l,r],必须存在左端点为l的线段和右端点为r的线段,否则无解。我们从完全处于[l,r]内部的线段中取出左端点最小的3个,从左到右依次称为A,B,C。其中A是必选的。发现如果C也被选且A与C有交,那B选不选都是一样的,也就是这种情况奇偶方案数相等。因此可以像上面删不好的线段一样把C也删掉(仅限此次询问)。接下来可以不断地把接下来最左的线段作为C,如果与A有交就把它删掉,直到C的左端点大于A的右端点为止。这时发现B也变成必选的了,不然中间会有一段填不上。所以可以令A=现在的B,B=现在的C,重复这个过程,直到无线段可取为止。
我们先把所有线段按左端点大小排序。对每个线段i处理出\(nxt_i\)表示完全在i右侧的最靠左的线段编号。对于一个询问[l,r],我们拿出此区间内最靠左的两个线段,并不断地用倍增跳它们的nxt数组,直到超出[l,r]的范围为止。根据上面描述的过程,跳nxt过程中走到的位置都是必选的,而没走到的都会在上面"把C删掉"的步骤中被删掉。如果最靠左的这两个线段最终跳到的位置都不能覆盖r,或者它们跳到相同的一个线段上(这说明中间有一段没覆盖上,画个图就知道了),那就是无解。否则可以根据跳到的线段总数判断答案是1还是-1。区间内只有1条或0条线段可取的情况要特判一下。
时间复杂度\(O(nlogn)\)。
点击查看代码
#include <bits/stdc++.h>
#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <int,int>
#define fi first
#define se second
#define pb push_back
#define mpr make_pair
void fileio()
{
#ifdef LGS
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
}
void termin()
{
#ifdef LGS
std::cout<<"\n\nEXECUTION TERMINATED";
#endif
exit(0);
}
using namespace std;
int n,q,nxt[200010][23];
vector <pii> v,inters;
map <int,int> isl,isr;
void initInters()
{
vector <int> bds;
rep(i,v.size()) bds.pb(v[i].fi-1),bds.pb(v[i].se);
sort(bds.begin(),bds.end());bds.erase(unique(bds.begin(),bds.end()),bds.end());
rep(i,bds.size()-1) inters.pb(mpr(bds[i]+1,bds[i+1]));
}
int main()
{
fileio();
cin>>n>>q;
int x,y;
rep(i,n)
{
scanf("%d%d",&x,&y);
v.pb(mpr(x,y));
}
sort(v.begin(),v.end(),[](pii xx,pii yy){if(xx.fi!=yy.fi) return xx.fi>yy.fi;return xx.se<yy.se;});
vector <pii> nv;
int curmn=2e9;
rep(i,v.size())
{
if(curmn<=v[i].se) continue;
curmn=v[i].se;nv.pb(v[i]);
}
v=nv;
sort(v.begin(),v.end());
rep(i,v.size()) isl[v[i].fi]=isr[v[i].se]=1;
initInters();
rep(i,v.size()-1)
{
int lb=i+1,ub=v.size()-1,mid;
while(lb<ub)
{
mid=(lb+ub)/2;
if(v[mid].fi>v[i].se) ub=mid;
else lb=mid+1;
}
if(v[lb].fi<=v[i].se) lb=0;
nxt[i][0]=lb;
}
rep(i,20) rep(j,v.size())
{
if(nxt[j][i]==0) nxt[j][i+1]=0;
else nxt[j][i+1]=nxt[nxt[j][i]][i];
}
//rep(i,v.size()) cout<<v[i].fi<<' '<<v[i].se<<endl;
rep(qn,q)
{
scanf("%d%d",&x,&y);
if(isl.find(x)==isl.end()||isr.find(y)==isr.end())
{
puts("0");
continue;
}
int pos=lower_bound(v.begin(),v.end(),mpr(x,-1))-v.begin();
if(pos==v.size()||v[pos].fi<x||v[pos].se>y)
{
puts("0");
continue;
}
if(pos+1==v.size()||v[pos+1].se>y)
{
if(v[pos].fi==x&&v[pos].se==y) puts("998244352");
else puts("0");
continue;
}
int pos2=pos+1,ans=2;
if(v[pos2].fi>v[pos].se)
{
puts("0");
continue;
}
for(int i=19;i>=0;--i) if(nxt[pos][i]>0&&v[nxt[pos][i]].se<=y) ans+=(1<<i),pos=nxt[pos][i];
for(int i=19;i>=0;--i) if(nxt[pos2][i]>0&&v[nxt[pos2][i]].se<=y) ans+=(1<<i),pos2=nxt[pos2][i];
if(v[pos].se!=y&&v[pos2].se!=y||pos==pos2)
{
puts("0");
continue;
}
if(ans%2==0) puts("1");
else puts("998244352");
}
termin();
}