E. Bipartite Segments
分类:
data structures
dfs and similar
题意: 给你一个无向图, n(1≤n≤3×105) 个点,保证没有偶数环,有 q(1≤q≤3×105) 次询问,每次给出 l r , 问只保留编号 [l,r] 区间的点的边,有多少个区间内点和它们的边构成的图是二分图?
解题思路: 判断一个无向图是否是二分图的重要性质就是——没有奇数度的环!因此,这样考虑,预处理出所有环(因为已经保证了没有偶数度环)的最小编号和最大编号,这个做法很多,类似tarjan
的方法是考虑用栈存一下当前的访问的编号,当访问到已经标记的点,则一直退栈同时更新最大值最小值(因为环内元素就是一个双连通分量!),对于每个查询,如果它不包含任何奇数度环,那么答案就是
∑i=lr(r−i+1)=(r−l+1)⋅(r−l+2)2
!而如果包含了奇数度环的话,考虑所有包含环的最右边的位置
p(p≤r)
,显然在
[p+1,r]
(如果
p+1≤r
)的答案就是前者的情况,而在
[l,p]
则重新考虑。又想,对于每个点
i
维护一个区间
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 10;
vector<int> g[maxn];
int vis[maxn];
stack<int> S;
int p[maxn];
ll sum[maxn];
void dfs(int u, int fa) {
S.push(u);
vis[u] = 1;
for (int i = 0; i < g[u].size(); i++) {
int v = g[u][i];
if (v != fa) {
if (vis[v] == 0) dfs(v, u);
else if (vis[v] == 1) {
int Max = u, Min = u;
while (!S.empty()) {
int w = S.top(); S.pop();
Max = max(Max, w); Min = min(Min, w);
if (w == v) break;
}
// printf("<%d %d\n", Min, Max);
p[Min] = Max;
}
}
}
if (!S.empty() && S.top() == u) S.pop();
vis[u] = 2;
}
int main() {
int n, m, q;
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int a, b;
scanf("%d%d", &a, &b);
g[a].push_back(b);
g[b].push_back(a);
}
memset(vis, 0, sizeof vis);
for (int i = 1; i <= n + 1; i++) p[i] = n + 1;
for (int i = 1; i <= n; i++) {
if (vis[i] == 0) dfs(i, -1);
}
sum[n + 1] = 0;
for (int i = n; i >= 1; i--) {
p[i] = min(p[i], p[i + 1]);
sum[i] = p[i] - i + sum[i + 1];
// printf("p[%d] %d - sum[%d] %I64d\n", i, p[i], i, sum[i]);
}
scanf("%d", &q);
while (q--) {
int l, r;
scanf("%d%d", &l, &r);
int L = l, R = r, P = l;
while (L <= R) {
int m = L + R >> 1;
if (p[m] <= r) L = m + 1;
else {
P = m;
R = m - 1;
}
}
printf("%I64d\n", sum[l] - sum[P] + 1LL * (r - P + 2) * (r - P + 1) / 2);
}
return 0;
}