CWOI 图论专题 1

发布时间 2023-11-21 16:40:04作者: xx019

1. CF1163F. Indecisive Taxi Fee

2. P2619 [国家集训队] Tree I

wqs 二分。

3. P5633 最小度限制生成树

4. P4180 [BJWC2010] 严格次小生成树

典。先随便求一颗最小生成树,一颗严格次小生成树一定是在它的基础上替换了一条边,因为多替换几次要么没影响等于没替换,要么会使得权值大于只替换一条的。枚举加上去的非树边,它与原树形成一个环,找这个环上除了它之外最大的且边权不同的边即可。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int fa[100005];
int find(int x){
	return ((x==fa[x])?x:fa[x]=find(fa[x]));
}
struct Node{
	int u,v,w;
	bool operator <(const Node &b)const{
		return w<b.w;
	}
}E[300005];
struct edge{
	int v,w,nxt;
}e[200005];
int tot,head[100005];
void add(int u,int v,int w){
	e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
struct Info{
	int x,y;
	Info():x(-inf),y(-inf){};
	Info(int _x,int _y):x(_x),y(_y){};
	Info operator +(const Info &b)const{
		if(x!=b.x)return (Info){max(x,b.x),max(max(y,b.y),min(x,b.x))};
		if(y!=b.y)return (Info){x,max(y,b.y)};
		return (Info){x,y};
	}
}f[22][100005];
int dep[100005],val[100005],pa[22][100005],used[300005];
void dfs(int u,int fa){
	dep[u]=dep[fa]+1,pa[0][u]=fa,f[0][u]=(Info){val[u],-inf};
	for(int i=1;i<=20;i++)pa[i][u]=pa[i-1][pa[i-1][u]];
	for(int i=1;i<=20;i++)f[i][u]=f[i-1][u]+f[i-1][pa[i-1][u]];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v,w=e[i].w;if(v==fa)continue;
		val[v]=w;dfs(v,u);
	}
}
Info ask(int u,int v){
	if(dep[u]>dep[v])swap(u,v);
	Info ans=(Info){-inf,-inf};
	for(int i=20;i>=0;i--){
		if(dep[v]-dep[u]>=(1ll<<i))ans=ans+f[i][v],v=pa[i][v];
	}
	if(u==v)return ans;
	for(int i=20;i>=0;i--){
		if(pa[i][u]!=pa[i][v])ans=ans+f[i][u],ans=ans+f[i][v],u=pa[i][u],v=pa[i][v];
	}
	ans=ans+f[0][u],ans=ans+f[0][v];
	return ans;
}
signed main(){
	int n=read(),m=read();
	for(int i=1,u,v,w;i<=m;i++){
		u=read(),v=read(),w=read();
		E[i]=(Node){u,v,w};
	}
	sort(E+1,E+m+1);
	for(int i=1;i<=n;i++)fa[i]=i;
	int cnt=0,sum=0;
	for(int i=1;i<=m;i++){
		int u=E[i].u,v=E[i].v,w=E[i].w;
		int fu=find(u),fv=find(v);
		if(fu==fv)continue;
		fa[fu]=fv,sum+=w,used[i]=1;
		add(u,v,w),add(v,u,w);
		if((++cnt)==n-1)break;
	}
	val[1]=-inf;dfs(1,0);
	int ans=inf;
	for(int i=1;i<=m;i++){
		if(used[i])continue;
		int u=E[i].u,v=E[i].v,w=E[i].w;
		Info tmp=ask(u,v);
		if(tmp.x==-inf)continue;
		if(tmp.x!=w)ans=min(ans,sum-tmp.x+w);
		else if(tmp.y!=-inf)ans=min(ans,sum-tmp.y+w);
	}
	printf("%lld\n",ans);
	return 0;
}

5. CF152E. Garden

比较板的斯坦纳树。

问题就是有 \(k\) 个关键点,需要通过一些点把这些关键点连起来,选一个点需要代价,求最小代价。显然最后图一定会连成一棵树,令 \(f_{i,j,S}\) 表示 \((i,j)\) 为所在连通块的根,连通块内包含 \(S\) 点集内的关键点的最小代价。转移分两种:

  • \((i,j)\) 只选了一个儿子。即 \(f_{i,j,S}\gets f_{x,y,S}+a_{i,j}\)

  • \((i,j)\) 选了多个儿子。即 \(f_{i,j,S+T}\gets f_{i,j,S}+f_{i,j,T}-a_{i,j}(S\cap T=\varnothing)\)

解释一下。虽然第一种转移似乎没有考虑 \((i,j)\) 是关键点,且 \(S\) 不包含 \((i,j)\) 的情况。但第二种转移是可以得到一个对的最小值的,且第一种转移也没有使答案错误的变小,所以这样还是没问题的。

第二种转移是简单的,只需要枚举子集。发现第一种转移会有环,无法确定转移顺序。但第一种转移不会改变点集,只需要从小到大枚举 \(S\),跑 dij/spfa 即可。复杂度 \(\mathcal{O}(3^knm+2^knm\log nm)\)\(\mathcal{O}(3^knm+2^kn^2m^2)\),似乎在一般图上 spfa 会有更小的常数。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
const int dx[]={1,-1,0,0},dy[]={0,0,1,-1};
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int n,m,k,vis[205],f[205][155],a[105][105];pii p[205][155];
int id(int x,int y){
	return (x-1)*m+y;
}
int getx(int v){
	return (v-1+m)/m;
}
int gety(int v){
	return v-((v-1)/m)*m;
}
int in(int x,int y){
	return (x>=1&&x<=n&&y>=1&&y<=m);
}
void spfa(int msk){
	queue<pii>q;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			vis[id(i,j)]=1,q.push(mk(i,j));
		}
	}
	while(!q.empty()){
		int x=q.front().fi,y=q.front().se;q.pop();vis[id(x,y)]=0;
		for(int i=0;i<4;i++){
			int nx=x+dx[i],ny=y+dy[i];
			if(!in(nx,ny))continue;
			if(f[id(nx,ny)][msk]>f[id(x,y)][msk]+a[nx][ny]){
				f[id(nx,ny)][msk]=f[id(x,y)][msk]+a[nx][ny];
				p[id(nx,ny)][msk]=mk(2,id(x,y));
				if(!vis[id(nx,ny)])q.push(mk(nx,ny)),vis[id(nx,ny)]=1;
			}
		}
	}
}
int res[105][105];
void solve(int i,int msk){
	int I=p[i][msk].fi,MSK=p[i][msk].se;
	if(I==-1){
		if(msk!=-1)res[getx(i)][gety(i)]=1;
		return;
	}
	if(I==1)solve(i,MSK),solve(i,msk-MSK);
	else res[getx(i)][gety(i)]=1,solve(MSK,msk);
}
signed main(){
	n=read(),m=read(),k=read();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			a[i][j]=read();
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			for(int t=0;t<(1ll<<k);t++){
				f[id(i,j)][t]=inf,p[id(i,j)][t]=mk(-1,-1);
			}
		}
	}
	for(int i=0,x,y;i<k;i++){
		x=read(),y=read();f[id(x,y)][1ll<<i]=a[x][y],p[id(x,y)][1ll<<i]=mk(-1,0);
	}
	for(int msk=1;msk<(1ll<<k);msk++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				for(int t=(msk-1)&msk;t;t=(t-1)&msk){
					if(f[id(i,j)][t]+f[id(i,j)][msk-t]-a[i][j]<f[id(i,j)][msk]){
						f[id(i,j)][msk]=f[id(i,j)][t]+f[id(i,j)][msk-t]-a[i][j];
						p[id(i,j)][msk]=mk(1,t);
					}
				}
			}
		}
		spfa(msk);
	}
	int ans=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(f[ans][(1ll<<k)-1]>f[id(i,j)][(1ll<<k)-1])ans=id(i,j);			
		}
	}
	printf("%lld\n",f[ans][(1ll<<k)-1]);
	solve(ans,(1ll<<k)-1);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(res[i][j])putchar('X');
			else putchar('.');
		}
		puts("");
	}
	return 0;
}