【牛客网】小w的魔术扑克
构造+离线+树状数组
有一道弱化版的题:【SCOI2010】连续攻击游戏
上面这道题,二分图最大匹配就可以了。当然这题也可以,传说中的\(n^3\)过1e5
不过,这还是好妙的一道题鸭!
part1.大致思路
首先,对于每张牌,在\(A[i]\)和\(B[i]\)之间连一条无向边。
那么最后形成的图一定是由若干联通块组成的。
不难发现,如果联通块是一棵树,显然树上有一个值是取不到的。(自己手动模拟一下吧)。
因此,对于每个询问区间\([l,r]\),若\([l,r]\)中有若干个值,它们刚好构成了一棵树,那么肯定不能构成顺子\([l,r]\)。
所以,我们只用判断区间\([l,r]\)中是否有若干个值构成一棵树即可。
part2.实现与流程
对于判断是否是一棵树,用并查集或者直接DFS都可以。
我们取出一棵树的最大点权Mx和最小点权Mi。不难发现,若一个区间只要包含了Mx和Mi,那么这个区间显然不能为顺子。
因此,我们可以想成:有若干条左端点为\(Mi[i]\),右端点为\(Mx[i]\)的约束线段,只要一个区间\([l,r]\)包含了至少一条约束线段,那么就是非法的。
把右端点排个序,离线+树状数组维护就可以了。
代码:
#include<bits/stdc++.h>
#define MAXN 100010
using namespace std;
int n,m,Q,A[MAXN],B[MAXN];
namespace p100{
int cnt,head[MAXN],tot,BIT[MAXN];
struct node{
int ed,last;
}G[MAXN<<2];
bool vis[MAXN],can[MAXN];
vector<int> S[MAXN];
vector<node> P[MAXN];
void DFS(int x,int fa,int &Mx,int &Mi,bool &is){
vis[x]=true;
Mx=max(Mx,x);
Mi=min(Mi,x);
for(int i=head[x];i;i=G[i].last){
int t=G[i].ed;
if(t==fa)continue;
if(vis[t]){
is=false;
continue;
}
DFS(t,x,Mx,Mi,is);
}
}
void Add(int st,int ed){
tot++;
G[tot]=node{ed,head[st]};
head[st]=tot;
}
void ADD(int i,int x){
while(i<=n)BIT[i]+=x,i+=i&-i;
}
int Query(int i){
int res=0;
while(i>=1)res+=BIT[i],i-=i&-i;
return res;
}
void work(){
for(int i=1;i<=m;i++){
Add(A[i],B[i]);
Add(B[i],A[i]);
}
for(int i=1;i<=n;i++){
if(vis[i])continue;
int Mx=0,Mi=2e9+7;
bool is=true;
DFS(i,0,Mx,Mi,is);
if(is==true)S[Mx].push_back(Mi);
}
scanf("%d",&Q);
for(int i=1;i<=Q;i++){
int l,r;
scanf("%d %d",&l,&r);
P[r].push_back(node{l,i});
}
for(int i=1;i<=n;i++){
for(int j=0;j<S[i].size();j++){
int l=S[i][j];
ADD(l,1);
}
for(int j=0;j<P[i].size();j++){
int id=P[i][j].last,l=P[i][j].ed;
if(Query(i)-Query(l-1)>=1)can[id]=false;
else can[id]=true;
}
}
for(int i=1;i<=Q;i++){
if(can[i])puts("Yes");
else puts("No");
}
}
}
int main() {
scanf("%d %d",&n,&m);
for(int i=1; i<=m; i++)scanf("%d %d",&A[i],&B[i]);
p100::work();
return 0;
}