「CF901C」Bipartite Segments 题解
提供一个 \(O(n+q \log n)\) 的做法。
感谢 @reanap 找到这道题并和我一起探讨研究(准确来说,都是她的思路)。在此膜拜。
虽然说 \(2300\) 是黑题确实有点假,但是这道题确实很有意思。
以下根据我和她做这道题的思路进行解题。
根据题目描述分析性质。
题目描述地很清楚,这个图没有偶环。众所周知,二分图不能有奇环。而我们需要求一个区间 \([l,r]\) 内有多少个子区间 \([x,y](l \leq x \leq y \leq r)\),使得节点 \(x,x+1,x+2,\cdots,y\) 构成的图是一个二分图。
考虑一个很简单的东西,如果区间 \([l,r]\) 内所有节点构成的一个图不能够构成一个二分图,那么区间 \([l-1,r]\) 内所有节点构成的一个图不能够构成一个二分图。
证明:区间 \([l,r]\) 内所有节点已经构成的一个图不能够构成一个二分图,新加入的节点 \(l-1\) 只会对图造成更大的负担,而不会分担不能染色的节点所面临的问题。
定义 \(nxt_l\) 为:\([l,nxt_l]\) 内所有节点能够构成一个二分图并且 \(nxt_l\) 最大。根据我们上文的分析,对于任意一个 \(i\),满足 \(nxt_i \geq nxt_{i-1}\)。现在的问题就是我们要如何求出 \(nxt\) 数组和解决查询了。
先考虑处理这个 \(nxt\) 数组。我们想找出 \(nxt\) 数组,就要快速判断一个区间内是否有环。定义 \(cou_i\) 为当前已经加入的点中,属于环 \(i\) 的节点有多少个。因为 \(nxt_i \geq nxt_{i-1}\),我们就用双指针 \(l,r\) 去维护当前确定的区间,每次加入一个点 \(p\) 属于环 \(q\)(或者不属于任何一个环,这个时候可以不用去管它),将 \(cou_q\) 加上 \(1\)。如果 \(cou_q\) 的大小已经等于环 \(q\) 的大小,这个时候就不会构成二分图了,\(r \gets r-1\),此时的 \(nxt_l=r\)。挪动指针 \(l\),删除一个点同理。其实这个地方也有点像莫队(
在一个图上找环是一件非常麻烦的事情,似乎并不怎么好找。有一个非常可疑的点仿佛还没有用完——这个图没有偶环。图没有偶环,说明不可能有两个奇环合并在一起(因为这样会构成一个偶环)。所以对于这道题来说没有两个环共用一条边。诶好像有点熟悉,这难道不就是仙人掌吗?
注:其实这个是非常有用的两个性质:
- 如果图没有偶环,图一定为仙人掌(但是仙人掌是可能有偶环的);
- 如果图没有奇环,图一定为二分图(反之亦然)。
于是我们确定了:这个图是由树,基环树(包含了环)与仙人掌构成的一个图(或者说,沙漠绿洲)。然而这并没有什么用,因为我们只需要两个环不会共用一条边的结论就能求出这个图里面的环了(
现在我们已经求出了 \(nxt\) 数组,找到最大的那个 \(s\),使得 \(nxt_s \leq r\)。这个时候 \([s+1,r]\)
内所有的区间都是合法的,可以计算;计算 \([l,s]\) 的方案数,我们首先计算一下 \(nxt\) 数组的前缀和,答案就是 \(sum_s -sum_{l-1} - \frac{(s-l+1)\times (s+l-2)}{2}\)。
另外,图中最多只有 \(\lfloor \dfrac{n}{3} \rfloor\) 个奇环,可以把数组开小一点。然后为什么我在 CF 上根本没看到评测给我返回 WA 啊。
代码细节挺少的,适合我这种傻逼选手,除了二分锅了看了下题解(
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
vector<LL> G[300005],cir[300005];
LL n,m,q,bel[300005],dep[300005],fa[300005],sz[100005],nxt[300005],cou[100005],sum[300005],cnt;
void get(LL x,LL y)
{
if(dep[x]<dep[y]) return ;
++cnt;
cir[cnt].push_back(y);
bel[y]=cnt;
while(x!=y) cir[cnt].push_back(x),bel[x]=cnt,x=fa[x];
sz[cnt]=cir[cnt].size();
}
void dfs(LL now,LL pre)
{
fa[now]=pre;
dep[now]=dep[pre]+1;
for(unsigned int i=0;i<G[now].size();++i)
{
LL to=G[now][i];
if(to==pre) continue;
if(dep[to]) get(now,to);
else dfs(to,now);
}
}
void avoidWarning()
{
while(q-->0)
{
LL ll,rr;
scanf("%lld %lld",&ll,&rr);
LL l=ll,r=rr,ans=ll;
while(l<=r)
{
LL mid=(l+r)>>1;
if(nxt[mid]<rr) l=mid+1,ans=mid;
else r=mid-1;
}
if(nxt[ans]>rr) --ans;
printf("%lld\n",(rr-ans)*(rr-ans+1)/2+sum[ans]-sum[ll-1]-(ans-ll+1)*(ans+ll-2)/2);
}
}
int main(){
scanf("%lld %lld",&n,&m);
for(LL i=1;i<=m;++i)
{
LL u,v;
scanf("%lld %lld",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
for(LL i=1;i<=n;++i) if(!dep[i]) dfs(i,0);
LL l=1,r=0;
while(l<=n)
{
while(r<n)
{
++r;
LL pos=bel[r];
++cou[pos];
if(cou[pos]==sz[pos])
{
--cou[pos];
--r;
break;
}
}
nxt[l]=r;
LL pos=bel[l];
--cou[pos];
++l;
}
for(LL i=1;i<=n;++i) sum[i]=sum[i-1]+nxt[i];
scanf("%lld",&q);
avoidWarning();
return 0;
}