一般来说,这种题目是给定一些信息,比如说边,然后这些信息会在一个时间段内出现。
一般来说需要离线,然后建立一棵以维护每个时刻信息的线段树,具体来说就是在每个节点维护一个vector。
#121. 「离线可过」动态图连通性
以经典例题来说,我们根据每条边出现的时刻将它插入到线段树上对应时间区间的节点上。
当我们查询时,就从上至下递归,依次加边,用一个并查集维护连通性。
然后需要注意的是,当我们离开一个区间时,我们需要撤销并查集的连边操作,因此不能使用路径压缩,而要使用按秩合并,具体来说就是用一个栈来记录进入这个区间后多连了那些边,然后倒序撤销即可。
#include <bits/stdc++.h>
#define fo(i,a,b) for (ll (i)=(a);(i)<=(b);(i)++)
#define fd(i,b,a) for (ll (i)=(b);(i)>=(a);(i)--)
#define eb emplace_back
#define lc (o<<1)
#define rc ((o<<1)|1)
using namespace std;
typedef long long ll;
typedef double db;
const int N = 5e5 + 5;
const ll mo = 998244353;
struct node {
int x, y;
};
node s[N];
vector<node> e[N * 4];
struct key {
int x, y, id;
};
vector<key> q[N * 4];
int n, a[5005][5005], f[N], sz[N], m, x, y, op, f1, f2, top, ans[N];
bool flag;
void del(int now) {
while (top > now) {
f1 = s[top].x;
f2 = s[top].y;
top--;
f[f2] = f2;
sz[f1] -= sz[f2];
}
}
int find(int x) {
if (x == f[x])
return x;
return find(f[x]);
}
void upd(int o, int l, int r, int x, int y, int u, int v) {
if (x <= l && r <= y) {
e[o].push_back((node) {
u, v
});
return;
}
int m = (l + r) >> 1;
if (x <= m)
upd(lc, l, m, x, y, u, v);
if (m < y)
upd(rc, m + 1, r, x, y, u, v);
}
void modify(int o, int l, int r, int x, int u, int v, int t) {
if (l == r) {
q[o].push_back((key) {
u, v, t
});
return;
}
int m = (l + r) >> 1;
if (x <= m)
modify(lc, l, m, x, u, v, t);
else
modify(rc, m + 1, r, x, u, v, t);
}
void ask(int o, int l, int r) {
int now = top;
for (int i = 0; i < (int)e[o].size(); i++) {
f1 = find(e[o][i].x);
f2 = find(e[o][i].y);
if (f1 == f2)
continue;
if (sz[f1] < sz[f2])
swap(f1, f2);
sz[f1] += sz[f2];
f[f2] = f1;
s[++top] = (node) {
f1, f2
};
}
if (l == r) {
for (int i = 0; i < (int)q[o].size(); i++) {
f1 = find(q[o][i].x);
f2 = find(q[o][i].y);
if (f1 == f2)
ans[q[o][i].id] = 1;
else
ans[q[o][i].id] = -1;
}
del(now);
return;
}
int m = (l + r) >> 1;
ask(lc, l, m);
ask(rc, m + 1, r);
del(now);
}
int main() {
// freopen("data.in","r",stdin);
scanf("%d %d", &n, &m);
fo(i, 1, n) f[i] = i, sz[i] = 1;
fo(i, 1, m) {
scanf("%d %d %d", &op, &x, &y);
if (x > y)
swap(x, y);
if (!op) {
a[x][y] = i;
}
if (op == 1) {
upd(1, 1, m, a[x][y], i - 1, x, y);
a[x][y] = 0;
}
if (op == 2) {
modify(1, 1, m, i, x, y, i);
}
}
fo(i, 1, n) fo(j, i + 1, n) if (a[i][j])
upd(1, 1, m, a[i][j], m, i, j);
ask(1, 1, m);
fo(i, 1, m) if (ans[i] == 1)
puts("Y");
else if (ans[i] == -1)
puts("N");
return 0;
}
P5787 二分图 /【模板】线段树分治
给一些加边和删边操作,询问每个时刻是不是二分图。
几乎跟上面没有区别,将每个点拆成两个点,i,i+n,给(x,y)连边的时候就合并(x,y+n),(x+n,y)即可。
#include<bits/stdc++.h>
#define fo(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define fd(i,b,a) for (int (i)=(b);(i)>=(a);(i)--)
#define eb emplace_back
#define lc (o<<1)
#define rc ((o<<1)|1)
#define A puts("Yes")
#define B puts("No")
using namespace std;
typedef long long ll;
typedef double db;
const int N=5e5+5;
int f[N],s[N],n,x,y,top,m,k,f1,f2,ans[N];
struct node{
int x,y;
};
node st[N];
vector<node> e[N*4];
int find(int x){
if (x==f[x]) return x;
return find(f[x]);
}
void merge(int x,int y){
f1=find(x); f2=find(y);
if (s[f1]<s[f2]) swap(f1,f2);
f[f2]=f1;
s[f1]+=s[f2];
st[++top]=(node){f1,f2};
}
void del(int now){
while (top>now) {
f1=st[top].x; f2=st[top].y; top--;
s[f1]-=s[f2];
f[f2]=f2;
}
}
void upd(int o,int l,int r,int x,int y,int u,int v){
if (x>y) return;
if (x<=l && r<=y) {
e[o].eb((node){u,v});
return;
}
int m=(l+r)>>1;
if (x<=m) upd(lc,l,m,x,y,u,v);
if (m<y) upd(rc,m+1,r,x,y,u,v);
}
void ask(int o,int l,int r){
int now=top;
bool flag=1;
for (int i=0;i<(int)e[o].size();i++) {
f1=find(e[o][i].x); f2=find(e[o][i].y);
if (f1==f2) {
flag=0;
break;
}
merge(e[o][i].x, e[o][i].y+n);
merge(e[o][i].x+n, e[o][i].y);
}
if (flag) {
if (l==r) {
ans[l]=1;
}
else {
int m=(l+r)>>1;
ask(lc,l,m);
ask(rc,m+1,r);
}
}
del(now);
}
int main(){
// freopen("data.in","r",stdin);
int l,r;
scanf("%d %d %d",&n,&m,&k);
fo(i,1,n*2) {
f[i]=i; s[i]=1;
}
fo(i,1,m) {
scanf("%d %d %d %d",&x,&y,&l,&r);
upd(1,1,k,l+1,r,x,y);
}
ask(1,1,k);
fo(i,1,k) if (ans[i]) A; else B;
return 0;
}
P2056 [ZJOI2007] 捉迷藏
给定一棵树,初始每个点的颜色都是黑色,需要支持两种操作
1 将某个点的颜色翻转
2 询问所有黑点中相距最远的两个点的距离。
首先关于树的直径有一个非常好的结论。
两棵树相连,新直径的两端点一定是原四个端点中的两个。
首先跟上面一样,将每个点打到对应的线段树时间节点上,然后我们用一个栈维护当前的直径,那么每次新加入一个节点,新的直径的端点肯定是这三个点中的两个,比较一下即可。
这里使用欧拉序+st表预处理来求lca。
#include<bits/stdc++.h>
#define fo(i,a,b) for (ll (i)=(a);(i)<=(b);(i)++)
#define fd(i,b,a) for (ll (i)=(b);(i)>=(a);(i)--)
#define eb emplace_back
#define pi pair<ll,ll>
#define mk(x,y) make_pair((x),(y))
#define lc (o<<1)
#define rc ((o<<1)|1)
#define A puts("Yes")
#define B puts("No")
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef double db;
const ll mo1 = 1e9 + 7;
const ll mo2 = 1e9 + 9;
const ll P = 131;
const ll Q = 13331;
const ll mo = 998244353;
const ll inf = 1ll << 60;
const int N = 1e6 + 100;
vector<int> e[N], p[N], v[N * 4];
int n, x, y, deep[N], d[N * 2], seq[N * 2], f[N * 2][21], g[N * 2], cnt, first[N];
int bz[N], top;
pi st[N];
void dfs(int x, int y) {
seq[++cnt] = x;
d[cnt] = deep[x];
f[cnt][0] = cnt;
first[x] = cnt;
for (int v : e[x]) {
if (v == y) continue;
deep[v] = deep[x] + 1;
dfs(v, x);
seq[++cnt] = x;
d[cnt] = deep[x];
f[cnt][0] = cnt;
}
}
int l, r, k;
int ask(int x, int y) {
l = first[x];
r = first[y];
if (l > r) swap(l, r);
k = g[r - l + 1];
if (d[f[l][k]] < d[f[r - (1 << k) + 1][k]])
return seq[f[l][k]];
return
seq[f[r - (1 << k) + 1][k]];
}
void upd(int o, int l, int r, int tl, int tr, int x) {
if (tl > tr) return;
if (tl <= l && r <= tr) {
v[o].eb(x);
return;
}
int m = (l + r) >> 1;
if (tl <= m) upd(lc, l, m, tl, tr, x);
if (m < tr) upd(rc, m + 1, r, tl, tr, x);
}
int dis(int x, int y) {
return deep[x] + deep[y] - 2 * deep[ask(x, y)];
}
void ask(int o, int l, int r) {
int now = top;
for (int x : v[o]) {
if (!top) st[++top] = mk(x, x);
else {
if (dis(x, st[top].fi) > dis(x, st[top].se)) {
if (dis(x, st[top].fi) > dis(st[top].fi, st[top].se)) {
++top;
st[top] = mk(x, st[top - 1].fi);
}
}
else {
if (dis(x, st[top].se) > dis(st[top].fi, st[top].se)) {
++top;
st[top] = mk(x, st[top - 1].se);
}
}
}
}
if (l == r) {
if (bz[l] != 0) {
if (top) {
printf("%d\n", dis(st[top].fi, st[top].se));
}
else puts("-1");
}
}
else {
int m = (l + r) >> 1;
ask(lc, l, m);
ask(rc, m + 1, r);
}
top = now;
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
scanf("%d", &n);
fo(i, 1, n - 1) {
scanf("%d %d", &x, &y);
e[x].eb(y);
e[y].eb(x);
}
dfs(1, 0);
fo(j, 1, 20) fo(i, 1, cnt) {
if (d[f[i][j - 1]] < d[f[min((ll)cnt, i + (1 << (j - 1)))][j - 1]]) f[i][j] = f[i][j - 1];
else f[i][j] = f[min((ll)cnt, i + (1 << (j - 1)))][j - 1];
}
g[1] = 0;
fo(i, 2, cnt) g[i] = g[i / 2] + 1;
int q;
scanf("%d", &q);
char s[20];
fo(i, 1, q) {
scanf("%s", s + 1);
if (s[1] == 'C') {
scanf("%d", &x);
p[x].eb(i);
}
else {
bz[i] = 1;
}
}
fo(i, 1, n) {
if (!p[i].size()) {
upd(1, 1, q, 1, q, i);
}
else {
upd(1, 1, q, 1, p[i][0] - 1, i);
for (int j = 0;j < (int)p[i].size();j++) {
if (j & 1) {
if (j == (int)p[i].size() - 1) upd(1, 1, q, p[i][j], q, i);
else upd(1, 1, q, p[i][j], p[i][j + 1] - 1, i);
}
}
}
}
ask(1, 1, q);
return 0;
}
P4219 [BJOI2014] 大融合
小强要在 N 个孤立的星球上建立起一套通信系统。这套通信系统就是连接 N 个点的一个树。
这个树的边是一条一条添加上去的。
在某个时刻,一条边的负载就是它所在的当前能够联通的树上路过它的简单路径的数量。
现在,你的任务就是随着边的添加,动态的回答小强对于某些边的负载的询问。
跟前面的维护连通性一样,假设我们现在要查询在t时刻(x,y)这条边的负载,那么假如我们在t时刻这里撤销了(x,y)这条边,那么答案就是两个并查集的大小相乘。
那么实际操作的话就是将这条边在的查询时刻的状态看作是不连通。
例如这条边是在a时刻连上的,我们在b时刻查询,那么我们就在[a,b-1],[b+1,Q]这两个区间插入这条边,这样既保证了在b时刻这条边不存在,也不会影响其它的时刻。
#include<bits/stdc++.h>
#define fo(i,a,b) for (ll (i)=(a);(i)<=(b);(i)++)
#define fd(i,b,a) for (ll (i)=(b);(i)>=(a);(i)--)
#define eb emplace_back
#define pi pair<ll,ll>
#define mk(x,y) make_pair((x),(y))
#define lc (o<<1)
#define rc ((o<<1)|1)
#define A puts("Yes")
#define B puts("No")
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef double db;
const ll mo1=1e9+7;
const ll mo2=1e9+9;
const ll P=131;
const ll Q=13331;
const ll mo=998244353;
const int N=2e5+5;
const ll inf=1ll<<60;
struct node{
ll x,y;
};
node c[N];
int n,q,f[N],tot;
ll sz[N];
char s[N];
vector<pi> e[N*4];
map<pi,int> h;
vector<int> v[N];
pi st[N];
int top;
void upd(int o,int l,int r,int tl,int tr,int u,int v){
if (tl>tr) return;
if (tl<=l && r<=tr) {
e[o].eb(mk(u,v));
return;
}
int m=(l+r)>>1;
if (tl<=m) upd(lc,l,m,tl,tr,u,v);
if (m<tr) upd(rc,m+1,r,tl,tr,u,v);
}
int x,y,f1,f2;
int find(int x){
if (x==f[x]) return x;
return find(f[x]);
}
void merge(int x,int y){
f1=find(x); f2=find(y);
if (f1==f2) return;
if (sz[f1]<sz[f2]) swap(f1,f2);
f[f2]=f1;
sz[f1]+=sz[f2];
st[++top]=mk(f1,f2);
}
void ask(int o,int l,int r){
int now=top;
for (auto it:e[o]) {
merge(it.fi, it.se);
}
if (l==r) {
if (c[l].x) {
printf("%lld\n",sz[find(c[l].x)]*sz[find(c[l].y)]);
}
// return;
}
else {
int m=(l+r)>>1;
ask(lc,l,m);
ask(rc,m+1,r);
}
while (top>now) {
x=st[top].fi; y=st[top].se;
sz[x]-=sz[y];
f[y]=y;
top--;
}
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
int x,y;
scanf("%d %d",&n,&q);
fo(i,1,n) f[i]=i,sz[i]=1;
fo(i,1,q) {
scanf("%s %d %d",s+1,&x,&y);
if (x>y) swap(x,y);
if (s[1]=='A') {
h[mk(x,y)]=i;
}
else{
c[i]=(node){x,y};
v[h[mk(x,y)]].push_back(i);
}
}
for (auto it:h) {
int id=it.second,x=it.first.first,y=it.first.second;
if (!v[id].size()) {
// printf("%d %d %d\n",id,x,y);
upd(1,1,q,id,q,x,y);
}
else {
upd(1,1,q,id,v[id][0]-1,x,y);
// printf("%d %d %d %d\n",id,v[id][0]-1,x,y);
for (int i=0;i<(int)v[id].size();i++) {
if (i!=(int)v[id].size()-1) {
upd(1,1,q,v[id][i]+1,v[id][i+1]-1,x,y);
// printf("%d %d %d %d\n",v[id][i]+1,v[id][i+1]-1,x,y);
}
else upd(1,1,q,v[id][i]+1,q,x,y);
}
}
}
ask(1,1,q);
return 0;
}
标签:cnt,int,线段,分治,st,define,小结,top,fo
From: https://www.cnblogs.com/ganking/p/18336072