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);
    }
}

猜你喜欢

转载自www.cnblogs.com/naruto-mzx/p/12194440.html