图论2:最小生成树相关

最小生成树

star way to heaven

どこでもドア

最小树形图

给定根,求有向图的最小生成树。
感性理解一下大概是每次贪心取连向除根外每个点的最小边,也就是可能的最优解,答案加上这些边权。
若此时无环,皆大欢喜。否则环上有人得换边,显然rt不在环上,不存在环套环,环环独立,那么把环缩成一个点,连向环上的边边权减去它连向的点当前选的最小边的边权,连向这个新点,再继续在新图上贪心直到成功即可。
复杂度O(nm).

HDU - 2121Ice_cream’s world II

どこでもドア
求任意根的最小树形图。新建点作为根向每个点连inf的边求最小树形图即可。记录一下哪条inf的边被用到就能知道真正的根。

//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
#include<set>
#include<map>
#define inf 0x7fffffff
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
const int N=10007;
typedef long long LL; 
typedef double db;
using namespace std;
int n,m,RT;

template<typename T> void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

struct edge {
	int u,v,id; LL w;
}e[N],e2[N];

LL d[N];
int id[N],vis[N],pr[N];
LL zhuliu(int n,int m,int rt) {
	LL rs=0;
	For(i,1,n) id[i]=i;
	for(;;) {
		For(i,1,n) d[i]=inf,id[i]=vis[i]=0;
		int vcnt=0,ecnt=0;
		For(i,1,m) if(e[i].w<d[e[i].v]) {
			d[e[i].v]=e[i].w;
			pr[e[i].v]=e[i].u;
			if(e[i].u==rt) RT=e[i].id;
		}
		For(i,1,n) if(i!=rt) {
			if(d[i]==inf) return -1;
			rs+=d[i];
			if(!id[i]) {
				int x=i;
				for(;x!=rt&&!id[x]&&vis[x]!=i;x=pr[x]) vis[x]=i;
				if(x!=rt&&!id[x]) vcnt++;
				for(;x!=rt&&!id[x];x=pr[x]) id[x]=vcnt;
			}
		}
		For(i,1,n) if(!id[i]) id[i]=++vcnt;
		if(vcnt==n) break;
		For(i,1,m) {
			int u=e[i].u,v=e[i].v,w=e[i].w;
			if(id[u]==id[v]) continue;
			e2[++ecnt]=(edge){id[u],id[v],e[i].id,w-d[v]};	
		}
		For(i,1,ecnt) e[i]=e2[i];
		n=vcnt; m=ecnt; rt=id[rt];
	}
	return rs;
}
 
int main() {
	//freopen("1.in","r",stdin);
	//freopen(".out","w",stdout);
	while(scanf("%d%d",&n,&m)!=EOF) {
		LL sum=0;
		For(i,1,m) {
			int u,v,w;
			read(u); read(v); read(w);
			e[i]=(edge){u+1,v+1,0,w}; sum+=w;
		}
		For(i,1,n) e[m+i]=(edge){n+1,i,i,sum+1};
		LL ans=zhuliu(n+1,n+m,n+1);
		if(ans==-1||ans-sum-1>sum) printf("impossible\n\n");
		else printf("%lld %d\n\n",ans-sum-1,RT-1);
	}
    return 0;
}

bzoj4349: 最小树形图

どこでもドア
只需要知道每个点第一次被攻打时间的顺序,后面的攻打直接取最小值。裸的朱刘算法。

复制个模板少复制了一句调主函数半小时,我怎么不去吃×呢。
心情极差,最近状态奇差,再弱智的题都打不对,随便什么代码都要调几小时,一头撞死算了。

kruskal重构树

[NOI2018]归程

どこでもドア
kruskal合并两个联通块时合并的边一定是联通块中权值最大的边,小于等于这条边的边所能联通的所有点在这个联通块中。
在合并两个联通块的时候新建一个点作为两个联通块代表点的父亲,权值为这条合并的边,那么从一个点x往上跳到最靠上的权值小于等于v的祖先,这个祖先的所有叶子节点就是从x走小于等于v的边能到达的点的集合。
所以这题以1为起点跑dijkstra找单源最短路,然后按权值从大到小排序kruskal重构树即可。

//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<set>
#define Formylove return 0
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define inf 1e18
const int N=1000007; 
typedef long long LL;
typedef double db;
using namespace std;
int T,n,m,Q,K,S;

template<typename T> void read(T &x) {
    char ch=getchar(); T f=1; x=0;
    while((ch!='-')&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

struct edge {
    int u,v,l,a;
    edge(){}
    edge(int u,int v,int a):u(u),v(v),a(a){}
    friend bool operator <(const edge&A,const edge&B) {
        return A.a>B.a;
    }
}e[N];

int ec,fir[N],nxt[N],to[N],val[N];
void add(int u,int v,int w) {
    nxt[++ec]=fir[u]; fir[u]=ec; to[ec]=v; val[ec]=w;
    nxt[++ec]=fir[v]; fir[v]=ec; to[ec]=u; val[ec]=w;
}

int fa[N];
int find(int x) { return x==fa[x]?x:fa[x]=find(fa[x]); } 

int f[N][20],vv[N];

int ec2,fi[N],nx[N],tt[N];
void ADD(int u,int v) {
    nx[++ec2]=fi[u]; fi[u]=ec2; tt[ec2]=v;
}

LL fans[N],dis[N];
void dfs(int x,int F) {
    f[x][0]=F;
    fans[x]=inf;
    if(x<=n) fans[x]=dis[x];
    For(i,1,19) f[x][i]=f[f[x][i-1]][i-1];
    for(int i=fi[x];i;i=nx[i]) {
        dfs(tt[i],x);
        fans[x]=min(fans[x],fans[tt[i]]);
    }
}

int totnode;
void kruskal(int n) {
    sort(e+1,e+m+1);
    totnode=n;
    For(i,1,n*2) fa[i]=i;
    For(i,1,m) {
        int x=e[i].u,y=e[i].v;
        int fx=find(x),fy=find(y);
        if(fx!=fy) {
            totnode++;
            vv[totnode]=e[i].a;
            ADD(totnode,fx); ADD(totnode,fy);
            fa[fx]=fa[fy]=totnode;
        }
    }
    dfs(totnode,0);
}

struct node {
    int u; LL d; 
    node(int u,LL d):u(u),d(d){}
    friend bool operator <(const node&A,const node&B) {
        return A.d>B.d;
    }
};

priority_queue<node>que;
int vis[N];
void dijkstra(int s) {
    For(i,1,n) dis[i]=inf,vis[i]=0; 
    dis[s]=0; 
    que.push(node(s,0));
    while(!que.empty()) {
        node tp=que.top();
        que.pop();
        if(vis[tp.u]||dis[tp.u]!=tp.d) continue; 
        vis[tp.u]=1;
        for(int i=fir[tp.u];i;i=nxt[i]) {
            int y=to[i];
            if(dis[y]>dis[tp.u]+val[i]) {
                dis[y]=dis[tp.u]+val[i];
                que.push(node(y,dis[y])); 
            }
        } 
    } 
}

void init() {
    ec=ec2=0;
    memset(fir,0,sizeof(fir));
    memset(fi,0,sizeof(fi));
}

int main() {
#ifdef ANS
    freopen(".in","r",stdin);
    freopen(".out","w",stdout);
#endif
    read(T);
    while(T--) {
        init();
        read(n); read(m);
        For(i,1,m) {
            int u,v,l,a;
            read(u); read(v); read(l); read(a);
            add(u,v,l);
            e[i]=edge(u,v,a);
        } 
        dijkstra(1);
        kruskal(n);
        read(Q); read(K); read(S);
        LL ans=0;
        while(Q--) {
            int v0,p0;
            read(v0); read(p0);
            int x=(v0+ans%n*K%n-1)%n+1;
            int p=(p0+ans%(S+1)*K%(S+1))%(S+1);
            Rep(i,19,0) if(f[x][i]&&vv[f[x][i]]>p) 
                x=f[x][i];
            ans=fans[x];
            printf("%lld\n",ans);
        }
    } 
    Formylove;
}

[IOI2018] werewolf 狼人

どこでもドア
kruskal重构树,问两棵树的某两个子树是否有交点,二维数点。

最小斯坦纳树

最小斯坦纳树大概是求一个图的包含给定点集的子图的最小生成树。
给定点集很小,一般sn<=10,可以用状压dp解决。
f [ x ] [ s ] f[x][s] f[x][s]表示把x看作根,s集合联通的最小代价。
考虑一棵树往外扩展的方式,把当前要扩展的点提出来看作根,只有一个儿子时直接连上这条边向外扩展,多个儿子时合并多棵子树。前种扩展跑最短路,后种dp合并。
复杂度 2 s n n l o g n + 3 s n n 2^{sn}nlogn+3^{sn}n 2snnlogn+3snn

bzoj2595: [Wc2008]游览计划]

どこでもドア
模板题,以前写的了。

//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
#include<set>
#include<map>
#define Formylove return 0
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
const int N=1050;
typedef long long LL;
typedef double db;
using namespace std;
int n,m,a[12][12],tot,sx,sy,f[12][12][N];

template<typename T>void read(T &x)  {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

struct pre {
    int i,j,s;
    pre(int i=0,int j=0,int s=0):i(i),j(j),s(s){}
}p[12][12][N];
#define pr pair<int,int>
#define fi first
#define se second

queue<pr>que;
int vis[12][12],tx[5]={0,0,1,-1},ty[5]={1,-1,0,0};
void spfa(int S) {
    while(!que.empty()) {
        pr x=que.front();
        vis[x.fi][x.se]=0;
        que.pop();
        For(t,0,3) {
            int xx=x.fi+tx[t],yy=x.se+ty[t];
            if(xx<1||xx>n||yy<1||yy>m) continue;
            if(f[xx][yy][S]>f[x.fi][x.se][S]+a[xx][yy]) {
                f[xx][yy][S]=f[x.fi][x.se][S]+a[xx][yy];
                p[xx][yy][S]=pre(x.fi,x.se,S);
                if(!vis[xx][yy])
                    que.push(make_pair(xx,yy)); 
            }
        }
    }
}

void dfs(int x,int y,int s) {
    pre pp=p[x][y][s];
    vis[x][y]=1;
    int xx=pp.i,yy=pp.j,ss=pp.s;
    if(xx==0&&yy==0) return;
    dfs(xx,yy,ss);
    if(x==xx&&y==yy) dfs(xx,yy,s-ss);
}

int main() {
#ifdef ANS
    freopen(".in","r",stdin);
    freopen(".out","w",stdout);
#endif
    read(n); read(m);
    memset(f,127/3,sizeof(f));
    int inf=f[0][0][0];
    For(i,1,n) For(j,1,m) {
        read(a[i][j]);
        if(!a[i][j]) {
            f[i][j][1<<tot]=0;
            tot++;
            sx=i; sy=j;
        }
    }
    int up=(1<<tot)-1;
    For(S,0,up) {
        For(i,1,n) For(j,1,m) {
            for(int ss=((S-1)&S);ss;ss=((ss-1)&S)) 
                if(f[i][j][ss]+f[i][j][S-ss]-a[i][j]<f[i][j][S]) {
                    f[i][j][S]=f[i][j][ss]+f[i][j][S-ss]-a[i][j];
                    p[i][j][S]=pre(i,j,ss);
                }
            if(f[i][j][S]!=inf) {
                vis[i][j]=1;
                que.push(make_pair(i,j));
            }
        }
        spfa(S);
    }
    printf("%d\n",f[sx][sy][up]);
    dfs(sx,sy,up);
    For(i,1,n) For(j,1,m) {
        if(!a[i][j]) putchar('x');
        else if(vis[i][j]) putchar('o');
        else putchar('_');
        if(j==m) puts("");
    }
    Formylove;
}

bzoj4006: [JLOI2015]管道连接

どこでもドア
仍然是个模板题,秉承最短路一定要用dijkstra的优良传统修改了我的模板。

//Achen
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=10007;
typedef long long LL;
typedef double db;
using namespace std;
int n,m,pc,jh[N],g[1025],dp[10245];

template<typename T>void read(T &x)  {
   char ch=getchar(); x=0; T f=1;
   while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
   if(ch=='-') f=-1,ch=getchar();
   for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

struct node {
   int x,dis;
   friend bool operator <(const node&A,const node&B) {
   	return A.dis>B.dis;
   }
};
priority_queue<node>que;

#define inf 1e9
int ecnt,fir[N],nxt[N],to[N],val[N];
void add(int u,int v,int w) {
   nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v; val[ecnt]=w;
   nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u; val[ecnt]=w;
}
   
int f[N][1025];
void dijkstra(int S) {
   while(!que.empty()) {
   	node tp=que.top();
   	que.pop();
   	int x=tp.x;
   	if(f[x][S]!=tp.dis) continue;
   	for(int i=fir[x];i;i=nxt[i]) {
   		int y=to[i];
   		if(f[y][S]>f[x][S]+val[i]) {
   			f[y][S]=f[x][S]+val[i];
   			que.push((node){y,f[y][S]});
   		}
   	}
   }
}

int main() {
   //freopen("4006.in","r",stdin);
   //freopen("4006.out","w",stdout);
   read(n); read(m); read(pc);
   For(i,1,m) {
   	int u,v,w;
   	read(u); read(v); read(w);
   	add(u,v,w);
   }
   For(x,1,n) For(s,0,(1<<pc)-1) f[x][s]=inf;
   For(i,1,pc) {
   	int x,c;
   	read(c); read(x);
   	f[x][1<<i-1]=0;
   	jh[c]|=(1<<i-1);
   }
   For(s,1,(1<<pc)-1) {
   	For(x,1,n) {
   		for(int ss=(s-1);ss;ss=((ss-1)&s))  
   			f[x][s]=min(f[x][s],f[x][ss]+f[x][s^ss]);
   		if(f[x][s]!=inf) que.push((node){x,f[x][s]});
   	}
   	dijkstra(s);
   }
   For(s,1,(1<<pc)-1) {
   	dp[s]=g[s]=inf;
   	For(x,1,n) g[s]=min(g[s],f[x][s]);
   }
   g[0]=dp[0]=0;
   For(s,0,(1<<pc)-1) {
   	dp[s]=g[s];
   	for(int ss=(s-1);ss;ss=((ss-1)&s)) {
   		int fl=1;
   		For(i,1,pc) {
   			if((jh[i]&ss)!=0&&(jh[i]&ss)!=jh[i]) { fl=0; break; }
   			if((jh[i]&(s^ss))!=0&&(jh[i]&(s^ss))!=jh[i]) { fl=0; break; }
   		}
   		if(fl) dp[s]=min(dp[s],dp[ss]+dp[s^ss]);
   	}
   }
   printf("%d\n",dp[(1<<pc)-1]);
   return 0;
}

曼哈顿距离最小生成树

Test2018.3.5:T3

どこでもドア
有结论曼哈顿距离最小生成树只需每个点向周围八个方向每个方向最近点连边跑最小生成树即可。详情见链接。
找八个方向距离最近的点,以北到东北(??)方向为例,即满足xi>=x,yi-y>=xi-x的xi+yi最小的点,
按x排序,按x-y建树状数组,树状数组求前缀最小值即可。
然后可以通过旋转把其他方向转换到这个方向同样操作,每次左旋45度。注意变换后放进树状数组的坐标值是最初的坐标。

//Achen
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=8e5+7;
typedef long long LL;
typedef double db;
using namespace std;
int n,m;
LL xx[N],yy[N],ans;

template<typename T>void read(T &x)  {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

struct pt {
	LL x,y; int id;
	friend bool operator <(const pt&A,const pt&B) { return A.x>B.x; }
}p[N];
LL dis(int a,int b) { return abs(xx[a]-xx[b])+abs(yy[a]-yy[b]); }

LL g[N][20],val[N];
int f[N][20],ecnt,fir[N],nxt[N],to[N];
void add(int u,int v,LL w) {
	nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v; val[ecnt]=w;
	nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u; val[ecnt]=w;
}

int R[N];
void dfs(int x,int fa) {
	f[x][0]=fa;
	R[x]=R[fa]+1;
	For(i,1,18) {
		f[x][i]=f[f[x][i-1]][i-1];
		g[x][i]=max(g[x][i-1],g[f[x][i-1]][i-1]);	
	}
	for(int i=fir[x];i;i=nxt[i]) if(to[i]!=fa) {
		g[to[i]][0]=val[i];
		dfs(to[i],x);
	}
}

LL lca(int x,int y) {
	LL rs=0;
	if(R[x]<R[y]) swap(x,y);
	Rep(i,18,0) if(R[f[x][i]]>=R[y]) {
		rs=max(rs,g[x][i]);
		x=f[x][i];
	} 
	if(x==y) return rs;
	Rep(i,18,0) if(f[x][i]!=f[y][i]) {
		rs=max(rs,max(g[x][i],g[y][i]));
		x=f[x][i],y=f[y][i];
	}
	return max(rs,max(g[x][0],g[y][0]));
}

struct edge {
	int u,v; LL w;
	friend bool operator <(const edge&A,const edge&B) {
		return A.w<B.w;
	}
}e[N*4];
int ec;

int fa[N];
int find(int x) { return x==fa[x]?x:fa[x]=find(fa[x]); }

void kruskal() {
	For(i,1,n) fa[i]=i;
	sort(e+1,e+ec+1);
	For(i,1,ec) {
		int u=e[i].u,v=e[i].v;
		if(find(u)!=find(v)) {
			fa[find(u)]=find(v);
			add(u,v,e[i].w);
			ans+=e[i].w;
		}
	}
}

struct data {
	LL v; int id;
}sum[N];
data operator +(const data&A,const data&B) { if(!B.id||(A.id&&A.v<B.v)) return A; return B; }

void add(int x,data v) {
	for(int i=x;i<=n;i+=(i&(-i)))
		sum[i]=sum[i]+v;
}

int qry(int x) {
	data rs=(data){0,0};
	for(int i=x;i;i-=(i&(-i)))
		rs=rs+sum[i];
	return rs.id; 
}

LL ls[N];
void build() {
	For(i,1,n) p[i]=(pt){xx[i],yy[i],i};
	For(cs,1,4) {
		sort(p+1,p+n+1);
		ls[0]=0;
		For(i,1,n) {
			sum[i]=(data){0,0};
			ls[++ls[0]]=p[i].x-p[i].y;
		}
		sort(ls+1,ls+ls[0]+1);
		int sz=unique(ls+1,ls+ls[0]+1)-(ls+1);
		For(i,1,n) {
			int t=lower_bound(ls+1,ls+sz+1,p[i].x-p[i].y)-ls;
			int rs=qry(t);
			if(rs) e[++ec]=(edge){rs,p[i].id,dis(rs,p[i].id)};
			data ad=(data){cs<=2?xx[p[i].id]+yy[p[i].id]:xx[p[i].id]-yy[p[i].id],p[i].id};
			add(t,ad);
		}
		For(i,1,n) p[i]=(pt){p[i].x-p[i].y,p[i].x+p[i].y,p[i].id};
	}
	kruskal();
	dfs(1,0);
}

int main() {
    freopen("analysis.in","r",stdin);
    freopen("analysis.out","w",stdout);
    read(n); 
    For(i,1,n) { read(xx[i]); read(yy[i]); }
    build();
    read(m);
    For(i,1,m) {
    	int x,y;
    	read(x); read(y);
    	printf("%lld\n",ans+dis(x,y)-lca(x,y));
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值