圆方树
圆方树
图的路径信息是很难处理的,因为有环.
如果我们需要处理的路径信息有特殊性质,可以考虑使用圆方树.
在圆方树中,定义方点和圆点.
其中,圆点就是原图中存在的点,编号为 $1$ ~ $\mathrm{n}$.
方点是我们加进去的,且一个点双联通分量对应一个方点.
这里给出具体建立圆点方点的代码:
void tarjan(int x) {
dfn[x]=low[x]=++scc;
S.push(x);
for(int i=hd[x];i;i=nex[i]) {
int v=to[i];
if(!dfn[v]) {
tarjan(v);
low[x]=min(low[x],low[v]);
// x 为割点,即 x 为这个点双分量的根( DFS 树中深度最小点)
if(low[v]==dfn[x]) {
int tmp;
++tot;
G[x].pb(tot);
// 建立一个方点,将方点与处理 x 的全部连边.
do {
tmp = S.top();
S.pop();
G[tot].pb(tmp);
se[tot].insert(val[tmp]);
} while(tmp!=v);
}
}
else low[x]=min(low[x], dfn[v]);
}
}
这里特别注意一下:若 $\mathrm{(x,y)}$ 这条边是割边,那么也同样要在 $(x,y)$ 之间建立方点.
虽然说可能和其他的点双方点定义冲突,但是做题时发现大多数时候都不会影响.
最后需要证明一下这个东西是一颗树,即无环且联通.
1. 连通性好判断,因为根据定义方式肯定是联通的.
2. 因为任意两个点只能属于同一个点双,不可能两个点同时属于两个,所以无环.
Tourists
来源:CF487E
建出圆方树,根据套路让每个方点仅维护儿子的最小值.
这种维护方式可以避免修改时碰到菊花图的情况.
由于询问的是不重复点的路径最小点权,可以将每个点双想成一个环.
显然,对于环上 $\mathrm{a,b}$ 两点,可以走两种圆弧,选最小值的圆弧.
用一个 $\mathrm{multiset}$ 和树链剖分+线段树维护一下即可.
#include <cstdio>
#include <vector>
#include <set>
#include <stack>
#include <cstring>
#include <algorithm>
#define N 200009
#define ll long long
#define pb push_back
#define ls now<<1
#define rs now<<1|1
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int inf=1000000009;
stack<int>S;
multiset<int>se[N];
vector<int>G[N];
int low[N],dfn[N],scc,Q;
int fa[N],top[N],dep[N],son[N],size[N],st[N],bu[N],mn[N<<2],cnt;
int hd[N], to[N<<1], nex[N<<1], val[N], tot, n, m, edges, cal[N];
void add(int u, int v) {
nex[++edges] = hd[u];
hd[u] = edges, to[edges] = v;
}
void tarjan(int x) {
dfn[x]=low[x]=++scc;
S.push(x);
for(int i=hd[x];i;i=nex[i]) {
int v=to[i];
if(!dfn[v]) {
tarjan(v);
low[x]=min(low[x],low[v]);
// x 为割点,即 x 为这个点双分量的根( DFS 树中深度最小点)
if(low[v]==dfn[x]) {
int tmp;
++tot;
G[x].pb(tot);
// 建立一个方点,将方点与处理 x 的全部连边.
do {
tmp = S.top();
S.pop();
G[tot].pb(tmp);
se[tot].insert(val[tmp]);
} while(tmp!=v);
}
}
else low[x]=min(low[x], dfn[v]);
}
}
void dfs1(int x, int ff) {
fa[x] = ff, size[x] = 1, dep[x] = dep[ff] + 1;
for(int i=0;i<G[x].size();++i) {
int v=G[x][i];
dfs1(v, x);
size[x] += size[v];
if(size[v] > size[son[x]]) son[x] = v;
}
}
void dfs2(int x,int tp) {
top[x]=tp;
st[x]=++cnt;
bu[st[x]]=x;
if(son[x]) {
dfs2(son[x], tp);
}
for(int i=0;i<G[x].size();++i) {
int v=G[x][i];
if(v==son[x]) continue;
dfs2(v, v);
}
}
int get_lca(int x,int y) {
while(top[x]!=top[y]) {
if(dep[top[x]] > dep[top[y]]) swap(x,y);
y=fa[top[y]];
}
return dep[x]<dep[y]?x:y;
}
void pushup(int now) {
mn[now]=min(mn[ls], mn[rs]);
}
void build(int l,int r,int now) {
mn[now]=inf;
if(l==r) {
mn[now]=cal[bu[l]];
return ;
}
int mid=(l+r)>>1;
build(l,mid,ls);
build(mid+1,r,rs);
pushup(now);
}
int query(int l,int r,int now,int L,int R) {
if(l>=L&&r<=R) {
return mn[now];
}
int mid=(l+r)>>1;
if(L<=mid&&R>mid)
return min(query(l,mid,ls,L,R), query(mid+1,r,rs,L,R));
else if(L<=mid)
return query(l,mid,ls,L,R);
else return query(mid+1,r,rs,L,R);
}
void modify(int l,int r,int now,int p,int v) {
if(l==r) {
mn[now]=v;
return ;
}
int mid=(l+r)>>1;
if(p<=mid) modify(l,mid,ls,p,v);
else modify(mid+1,r,rs,p,v);
pushup(now);
}
int qpath(int x,int y) {
int re=inf;
while(top[x]!=top[y]) {
if(dep[top[x]]>dep[top[y]]) {
swap(x,y);
}
re=min(re, query(1,tot,1,st[top[y]],st[y]));
y = fa[top[y]];
}
if(dep[x] > dep[y]) swap(x, y);
return min(re, query(1, tot, 1, st[x], st[y]));
}
int main() {
// setIO("input");
scanf("%d%d%d",&n,&m,&Q);
for(int i=1;i<=n;++i) {
scanf("%d",&val[i]);
}
for(int i=1;i<=m;++i) {
int x,y;
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
tot=n, tarjan(1);
// root=1;
for(int i=n+1;i<=tot;++i) {
cal[i]=(*se[i].begin());
}
for(int i=1;i<=n;++i) {
cal[i]=val[i];
}
dfs1(1, 0);
dfs2(1, 1);
build(1, tot, 1);
for(int i=1;i<=Q;++i) {
char op[2];
int x,y,z;
scanf("%s",op);
if(op[0]=='C') {
scanf("%d%d",&x,&y);
// val[x] -> y
if(fa[x]) {
se[fa[x]].erase(se[fa[x]].find(val[x]));
se[fa[x]].insert(y);
cal[fa[x]]=*se[fa[x]].begin();
modify(1, tot, 1, st[fa[x]], cal[fa[x]]);
}
val[x]=cal[x]=y;
modify(1,tot,1,st[x],cal[x]);
}
else {
scanf("%d%d",&x,&y);
// x->y 的询问.
int lca=get_lca(x, y);
if(lca <= n) {
printf("%d\n", qpath(x, y));
}
else {
printf("%d\n", min(qpath(x, y), cal[fa[lca]]) ) ;
}
}
}
return 0;
}
EntropyIncreaser 与 动态图
动态求桥:
如果一条边在一个环上,则一定不是桥.
这个用 $\mathrm{LCT}$ 打一个标记即可.
动态求割点:
将图进行点双缩点,会形成树形结构.
考虑如何统计对于 $\mathrm{(x,y)}$ 之间割点的答案.
每一个点双对答案的贡献就是点双在该路径上的左右两端点.
如果是静态的,可以对于每个点双建立方点,然后连边.
$\mathrm{(x,y)}$ 的答案就是圆点个数.
现在由于是强制在线,所以用 $\mathrm{LCT}$ 动态维护这个过程即可.
#include <cstdio>
#include <vector>
#include <cstring>
#include <set>
#include <map>
#include <algorithm>
#define N 200009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
namespace A {
#define ls s[x].ch[0]
#define rs s[x].ch[1]
struct UFS {
int p[N];
void init() {
for(int i=0;i<N;++i) p[i]=i;
}
int find(int x) { return p[x]==x?x:p[x]=find(p[x]); }
int merge(int x, int y) {
x = find(x);
y = find(y);
if(x == y) return 0;
p[x] = y;
return 1;
}
}T;
struct data {
int ch[2], f, rev, si, v, tag;
data() {
ch[0]=ch[1]=f=rev=si=v=tag=0;
}
}s[N];
int sta[N], tot;
int isr(int x) { return s[s[x].f].ch[0]!=x&&s[s[x].f].ch[1]!=x; }
int get(int x) { return s[s[x].f].ch[1]==x; }
void pushup(int x) {
s[x].si=s[ls].si+s[rs].si+s[x].v;
}
void mark1(int x) {
if(!x) return ;
swap(ls, rs), s[x].rev^=1;
}
void mark2(int x) {
if(!x) return ;
s[x].tag=1;
s[x].v=s[x].si=0;
}
void pushdown(int x) {
if(s[x].rev) {
if(ls) mark1(ls);
if(rs) mark1(rs);
}
if(s[x].tag) {
if(ls) mark2(ls);
if(rs) mark2(rs);
}
s[x].rev=s[x].tag=0;
}
void rotate(int x) {
int old=s[x].f,fold=s[old].f,which=get(x);
if(!isr(old))
s[fold].ch[s[fold].ch[1]==old]=x;
s[old].ch[which]=s[x].ch[which^1];
if(s[old].ch[which])
s[s[old].ch[which]].f=old;
s[x].ch[which^1]=old,s[old].f=x,s[x].f=fold;
pushup(old),pushup(x);
}
void splay(int x) {
int v=0,u=x,fa;
for(sta[++v]=u; !isr(u); u = s[u].f) {
sta[++v] = s[u].f;
}
for(;v;--v) pushdown(sta[v]);
for(u=s[u].f;(fa=s[x].f)!=u;rotate(x))
if(s[fa].f!=u)
rotate(get(x)==get(fa)?fa:x);
}
void access(int x) {
for(int y=0;x;y=x,x=s[x].f)
splay(x),rs=y,pushup(x);
}
void makert(int x) {
access(x),splay(x),mark1(x);
}
void split(int x,int y) {
makert(x),access(y),splay(y);
}
void link(int x, int y) {
makert(x);
s[x].f = y;
}
void ADD(int x, int y) {
if(T.merge(x, y)) {
++tot;
s[tot].v = 1;
s[tot].si = 1;
link(x, tot);
link(tot, y);
}
else {
// 联通.
split(x, y);
// y is root of splay.
mark2(y);
}
}
int query(int x, int y) {
if(T.find(x) != T.find(y)) {
return -1;
}
else {
split(x, y);
return s[y].si;
}
}
void init(int poi) {
tot = poi;
}
#undef ls
#undef rs
};
// A: 求 (x,y) 之间有几个桥.
// B: 求 (x,y) 之间有几个割点.
namespace B {
#define ls s[x].ch[0]
#define rs s[x].ch[1]
struct UFS {
int p[N];
void init() {
for(int i=0;i<N;++i) p[i]=i;
}
int find(int x) { return p[x]==x?x:p[x]=find(p[x]); }
int merge(int x, int y) {
x = find(x);
y = find(y);
if(x == y) return 0;
p[x] = y;
return 1;
}
}T;
struct data {
int ch[2],f,rev,v,si;
data() {
ch[0]=ch[1]=f=rev=v=si=0;
}
}s[N];
int sta[N], tot;
int isr(int x) { return s[s[x].f].ch[0]!=x&&s[s[x].f].ch[1]!=x; }
int get(int x) { return s[s[x].f].ch[1]==x; }
void mark1(int x) {
if(!x) return ;
swap(ls, rs), s[x].rev^=1;
}
void pushup(int x) {
s[x].si=s[ls].si+s[rs].si+s[x].v;
}
void pushdown(int x) {
if(s[x].rev) {
if(ls) mark1(ls);
if(rs) mark1(rs);
}
s[x].rev = 0;
}
void rotate(int x) {
int old=s[x].f,fold=s[old].f,which=get(x);
if(!isr(old))
s[fold].ch[s[fold].ch[1]==old]=x;
s[old].ch[which]=s[x].ch[which^1];
if(s[old].ch[which])
s[s[old].ch[which]].f=old;
s[x].ch[which^1]=old,s[old].f=x,s[x].f=fold;
pushup(old),pushup(x);
}
void splay(int x) {
int v=0,u=x,fa;
for(sta[++v]=u; !isr(u); u = s[u].f) {
sta[++v] = s[u].f;
}
for(;v;--v) pushdown(sta[v]);
for(u=s[u].f;(fa=s[x].f)!=u;rotate(x))
if(s[fa].f!=u)
rotate(get(x)==get(fa)?fa:x);
}
void access(int x) {
for(int y=0;x;y=x,x=s[x].f)
splay(x),rs=y,pushup(x);
}
void makert(int x) {
access(x),splay(x),mark1(x);
}
void split(int x,int y) {
makert(x),access(y),splay(y);
}
void link(int x, int y) {
makert(x);
s[x].f = y;
}
void init(int poi) {
tot = poi;
for(int i=1;i<=poi;++i) {
s[i].v=s[i].si=1;
}
}
vector<int>det;
void dfs(int x) {
pushdown(x);
if(ls) dfs(ls);
if(rs) dfs(rs);
s[x].si=s[x].v;
det.pb(x);
s[x].ch[0]=s[x].ch[1]=s[x].f=s[x].rev=0;
}
void ADD(int x, int y) {
if(T.merge(x, y)) {
// (x, y) 之前并未联通.
link(x, y);
}
else {
// (x, y) 已经联通.
split(x, y);
dfs(y);
++tot;
s[tot].v=s[tot].si=0;
for(int i=0;i<det.size();++i) {
s[det[i]].f = tot;
}
det.clear();
}
}
int query(int x, int y) {
if(T.find(x) != T.find(y)) {
return -1;
}
else {
split(x, y);
return s[y].si;
}
}
#undef ls
#undef rs
};
int main() {
// setIO("input");
// freopen("input.out","w",stdout);
int n ,Q;
// 3 操作出问题了.
scanf("%d%d",&n,&Q);
A::init(n);
B::init(n);
A::T.init();
B::T.init();
int lastans = 0;
for(int i=1;i<=Q;++i) {
int op,x,y;
scanf("%d%d%d",&op,&x,&y);
x ^= lastans;
y ^= lastans;
if(op == 1) {
A::ADD(x, y);
B::ADD(x, y);
continue;
}
int o = 0;
if(op == 2) {
if(x == y) {
printf("%d\n", o = 0);
}
else {
printf("%d\n",o = A::query(x, y));
}
}
if(op == 3) {
if(x == y) {
printf("%d\n", o = 1);
}
else {
printf("%d\n",o = B::query(x, y));
}
}
lastans = (o == -1 ? lastans : o);
}
return 0;
}
小C的独立集
仙人掌上的 DP.
由于每条边最多只在一个环中,所以可以把环都拆出来,然后在环上 DP.
具体做法就是边跑点双边找到环的“根”,然后拿出这个环并 DP 即可.
#include <cstdio>
#include <vector>
#include <stack>
#include <cstring>
#include <algorithm>
#define N 100009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const int inf=10000000;
int n,m,scc;
vector<int>G[N];
int low[N], dfn[N], fa[N], f[N][2];
void calc(int x, int v) {
int f0,f1;
f0=f[v][0],f1=f[v][1];
for(int i=fa[v];i!=fa[x];i=fa[i]) {
int d0 = f0;
int d1 = f1;
f0 = f[i][0] + max(d0,d1);
f1 = f[i][1] + d0;
}
f[x][0] = f0;
// 强制 x 选,则 v 一定不能选.
f0=f[v][0],f1=-inf;
for(int i=fa[v];i!=fa[x];i=fa[i]) {
int d0=f0;
int d1=f1;
f0=f[i][0]+max(d0,d1);
f1=f[i][1]+d0;
}
f[x][1] = f1;
}
void tarjan(int x, int ff) {
fa[x]=ff;
// S.push(x);
low[x]=dfn[x]=++scc;
f[x][0] = 0, f[x][1] = 1;
for(int i=0;i<G[x].size();++i) {
int v=G[x][i];
if(v == ff) continue;
if(!dfn[v]) {
tarjan(v, x);
low[x]=min(low[x], low[v]);
}
else low[x]=min(low[x], dfn[v]);
if(low[v] > dfn[x]) {
// 这不是环上的点.
f[x][0] += max(f[v][0], f[v][1]);
f[x][1] += f[v][0];
}
}
for(int i=0;i<G[x].size();++i) {
int v=G[x][i];
if(fa[v] != x && dfn[v] > dfn[x]) {
calc(x, v);
}
}
}
int main() {
// setIO("input");
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i) {
int x,y;
scanf("%d%d",&x,&y);
G[x].pb(y);
G[y].pb(x);
}
int ans = 0;
for(int i=1;i<=n;++i) {
if(!dfn[i]) {
tarjan(i, 0);
ans += max(f[i][0], f[i][1]);
}
}
printf("%d\n", ans);
return 0;
}

浙公网安备 33010602011771号