Curing Kingdom

Description

给定无向连通图,每个点有代价。
选择非空点集 S 付费,之后可不断免费“治愈”与已治愈点相邻的点,但要求任意时刻尚未治愈的点集必须连通。
求最小总付费代价。

Solution

Hint1

考虑部分分,原图为一棵树时,答案显然是点权最大的叶子不选,其他叶子都选。

Hint2

对于一般图,因为和连通性有关,考虑点双连通分量。

Hint3

同理,考虑对于缩点后位于叶子的点双,也是点双中最小点权最大的叶子不选,其他叶子都选。

Hint4

对于构造方案,使用双极定向。

四个等价性质:

  • 添加无向边 \((S,T)\) 后,图点双连通
  • 圆方树中所有方点构成一条链,\(S\)\(T\) 是圆方树的一条直径
  • 存在一种对图的边定向的方法,使得 \(S\) 没有入度,\(T\) 没有出度,其他点出入度都非 \(0\)
  • 存在一个所有点的排列 \(p\),使得 \(p_1=S,p_n=T\),且任意前缀/任意后缀的导出子图连通

建立一个超级源点 \(S\),向所有开始就选了的节点连边,以那个点权最大的叶子作为 \(T\) 跑双极定向:

双极定向
  • \(S\) 为根建立 DFS 树
  • \((T,1)\) 加入空队列
  • 如果队列非空:
    • 取出队首 \((p,d)\)
    • 不断跳父亲直到根 \(S\):
      • 记当前点为 \(u\),父亲为 \(f\)
      • 若该边已经被定向,结束循环
      • 如果 \(d=1\)\(f\to u\),否则 \(u\to f\)
      • 遍历 \(f\) 的所有前向边 \((f,v)\),若 \(v\)\(u\) 子树内:
        • 如果 \(d=1\)\(f\to v\),否则 \(v\to f\)
        • \((v,1-d)\) 加入队列

然后拓扑排序得到最终的结果,写的好的话时间复杂度 \(\mathcal O(n+m)\)

注意特判仅有一个点双连通分量和 \(n=1\) 的情况。

点击查看代码
#include<bits/stdc++.h>
#define FASTIO ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define rep(i,l,r) for(int i=(l); i<=(r); ++i)
#define drep(i,r,l) for(int i=(r); i>=(l); --i)
using namespace std;
const int N=5e5+5;
int _,n,m,t,T,a[N],p[N],c[N],deg[N],fa[N],L[N],R[N];
int dfc,top,cnt,dfn[N],low[N],stk[N];
basic_string<int>E[N],D[N],G[N];
map<int,int>S[N];
inline void chk(int&x,int y) {x=x<y?x:y;}
inline void DFS(int u) {
	dfn[u]=low[u]=++dfc,stk[++top]=u;
	for(int v:E[u]) {
		if(!dfn[v]) {
			DFS(v),chk(low[u],low[v]);
			if(dfn[u]<=low[v]) {
				D[++cnt]+=u,++deg[u];
				do {t=stk[top--],D[cnt]+=t,++deg[t];} while(v^t);
			}
		} else chk(low[u],dfn[v]);
	}
}
inline void sol(int u) {
	L[u]=++dfc;
	for(int v:E[u]) {
		if(v==fa[u]) continue;
		if(!L[v]) fa[v]=u,sol(v);
		else if(L[v]<L[u]) G[v]+=u;
	}
	R[u]=dfc;
}
inline void sol() {
	cin>>n>>m,dfc=top=cnt=T=0;
	rep(i,1,n) cin>>a[i];
	rep(i,0,n) E[i].clear(),D[i].clear(),G[i].clear(),S[i].clear(),p[i]=c[i]=deg[i]=fa[i]=L[i]=R[i]=dfn[i]=0;
	rep(i,1,m) {
		int u,v;
		cin>>u>>v,E[u]+=v,E[v]+=u;
	}
	if(n==1) {
		cout<<1<<'\n'<<1<<'\n';
		return;
	}
	DFS(1);
	rep(i,1,cnt) for(int u:D[i]) if(deg[u]<2&&a[u]<a[p[i]]) p[i]=u;
	rep(i,1,cnt) {
		for(int u:D[i]) if(deg[u]>1) ++c[i];
		if(c[i]<2) T=!T||a[T]<a[p[i]]?p[i]:T;
	}
	rep(i,1,cnt) if(c[i]<2&&T^p[i]) E[0]+=p[i],E[p[i]]+=0;
	if(!E[0].size()) E[0]+=T,E[T]+=0,T=T==1?2:1;
	dfc=0,sol(0);
	queue<pair<int,int>>q;
	q.emplace(T,1);
	while(q.size()) {
		auto[p,d]=q.front(); q.pop();
		for(int u=p; u; u=fa[u]) {
			int f=fa[u];
			if(S[f].count(u)) break;
			S[f][u]=d,S[u][f]=d^1;
			for(int v:G[f]) if(!S[f].count(v)&&L[v]<=R[u]&&L[v]>=L[u])
				S[f][v]=d,S[v][f]=d^1,q.emplace(v,d^1);  
		}
	}
	queue<int>Q;
	cout<<E[0].size()<<'\n';
	for(int t:E[0]) Q.push(t);
	memset(deg,0,n+1<<2);
	rep(i,1,n) for(int v:E[i]) if(S[i][v]) ++deg[v];
	while(Q.size()) {
		int u=Q.front(); Q.pop();
		cout<<u<<' ';
		for(int v:E[u]) if(S[u][v]) if(!--deg[v]) Q.push(v);
	}
	cout<<'\n';
}
signed main() {
	FASTIO;
	cin>>_,a[0]=1e9;
	while(_--) sol();
}
posted @ 2026-06-08 10:44  in-bustle  阅读(34)  评论(1)    收藏  举报