luogu CF901C Bipartite Segments |Tarjan+二分
题意翻译
给你一个有nnn个点的无向图,没有偶环。我们把节点标记为1..n1..n1..n。
你需要回答qqq个询问,每一个询问由一个区间L,R组成,你需要计算出有多少个点对x,y,满足由[x,y]之间的所有点组成的子图是一个二分图。
输入数据第一行两个整数 n,mn,mn,m (1≤n≤3×105,1≤m≤3×1051\le n\le 3\times 10^5,1\le m\le 3\times10^51≤n≤3×105,1≤m≤3×105)代表点数和边数 接下来的mmm行,每行两个整数,表示一条无向边,保证无自环,保证无重边
一个整数 q(1≤q≤3×105)q(1\le q\le3\times 10^5)q(1≤q≤3×105),表示询问个数
接下来qqq行,每行一个询问[L,R]
对于每个询问,输出一个整数,表示满足条件的点对[x,y]的个数
设一个cnt[i]表示i往左最多可以扩展到哪个下标
可以发现cnt必然是 不下降 的序列
对于询问[x,y]中的一个点i,若cnt[i]>=x 则答案贡献为 i-cnt[i]+1
若cnt[i]<x则答案贡献为i-x+1,
而cnt是递增的,我们只需要二分找到分界点x的下标 O(logn)
对于第一种情况,我们可以前缀和求 O(1)
对于第二种情况,我们可以等差数列求和 O(1)
怎么预处理cnt[i]呢?
先用Tarjan求出一个环内最小编号最大编号,然后这个环的最大编号的cnt[i]等于最小编号
其它情况的cnt可以直接递推
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
#define ll long long
void qmax(int &x, int y){
if (x < y)x = y;
}
void qmin(int &x, int y){
if (x > y)x = y;
}
const int _=6e5+10;
int nxt[_<<1],head[_],go[_<<1],tot;
inline void add(int u,int v){
nxt[++tot]=head[u];head[u]=tot;go[tot]=v;
nxt[++tot]=head[v];head[v]=tot;go[tot]=u;
}
int st[_],top;
int vis[_],loop[_];
int n,m,cnt[_],sum[_];
void dfs(int x,int fa){
vis[x]=1;
st[++top]=x;
for (int i=head[x];i;i=nxt[i])
if (go[i]!=fa && !loop[go[i]]){
if (!vis[go[i]]) dfs(go[i],x);
else{
int mx=0,mn=n+1;
for (int j=top,y;;j--)
{
y=st[j];
qmax(mx,y);
qmin(mn,y);
loop[y]=1;
if (st[j]==go[i]) break;
}
if (!cnt[mx]) cnt[mx]=mn;
else qmax(cnt[mx],mn);
}
}
--top;
}
signed main(){
cin>>n>>m;
for(int i=1,u,v;i<=m;i++){
scanf("%lld%lld",&u,&v);
add(u,v);
}
for(int i=1;i<=n;i++)
if(!vis[i])dfs(i,i);
cnt[0]=0;
for(int i=1;i<=n;i++){
qmax(cnt[i],cnt[i-1]);
sum[i]=sum[i-1]+cnt[i];
}
int q,x,y,ans,mid; cin>>q;
while (q--){
scanf("%lld%lld",&x,&y);
int l=x,r=y,s=y+1;
while (l<=r){
mid=(l+r)>>1;
if (cnt[mid]>=x)
r=mid-1,s=mid;
else l=mid+1;
}
ans=(ll)(y+x)*(y-x+1)/2-(sum[y]-sum[s-1])-(ll)(x-1)*(s-x);
printf("%lld\n",ans);
}
}
不以物喜,不以己悲