图论

2021 1|2

10064. 「一本通 3.1 例 1」黑暗城堡

mind

这个题显然是求最短路径树的个数

最短路径树:对于根节点u,它到任意v的最小距离等于树上的距离

考虑(dijkstra),每次选择一个与起始节点最小的点加入集合,所以选择的边组成的集合就是一种最短路径树,至于统计方案,我们可以判断每一条边,如果(dis[v]=dis[u]+e[i])方案数可以累乘,不过如果更新(dijkstra)需要重置到这一点的最短路方案数。当然我们也可以在跑完(dijkstra)后统计答案。

/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <cstring>
#define int long long 
using namespace std;
const int M = 5e5;
const int inf = 0x3f3f3f;
int n,m,mod,js,head[1005];
struct edge{
   int u,v,w,nxt;
}e[M << 1];
void add_edge(int u,int v,int w){
      e[++js].v = v;
      e[js].w = w;
      e[js].nxt = head[u];
      head[u] = js;
}
struct node{
	int u,w;
	bool operator < (const node a)const{
	     return w > a.w;
	}
};
bool vis[1005];
int dis[1005],ans[1005];
priority_queue<node> q;
void dij(int s){
	memset(dis, inf,sizeof(dis));
	q.push((node){s,0});
	dis[s] = 0;ans[1] = 1;
	while(!q.empty()){
		node tp = q.top();q.pop();
		int u = tp.u;
		if(vis[u])continue;
		vis[u] = 1;
		for(int i = head[u]; i; i = e[i].nxt){
			 int v = e[i].v;
			 if(dis[v] > dis[u] + e[i].w){
			 	 ans[v] = 1;dis[v] = dis[u] + e[i].w;
			 	 q.push((node){v,dis[v]});
			 }
			 else if(dis[v] == dis[u] + e[i].w)
			 	  ans[v]++;
		}
	}
	return;
}
signed main(){
   std::ios::sync_with_stdio(false);
   mod = pow(2,31) - 1;
   cin>>n>>m;
   for(int i = 1,u,v,w;i <= m; i++){
   	   cin>>u>>v>>w;
   	   add_edge(u, v, w);
   	   add_edge(v, u, w);
   }
   dij(1);
   int Ans = 1;
   for(int i = 1;i <= n; i++){
   	   Ans *= ans[i]%mod;
   	   Ans %= mod;
   }
   cout<<Ans%mod;
}

一本通 3.1 例 2」北极通讯网络

mind

逆向思维,把所有可以相互连通的村庄连接起来,构成一个图,卫星台数就是图中连通支的个数。

问题转化为:找到一个最小的(d),使得把所有权值大于(d)的边去掉之后,连通支的个数(<=k)

定理1

如果去掉所有权值大于d的边后,最小生成树被分割成为(k)个连通支,则图也被分割成为(k)个连通支

证明用反证法

所以该题的解就是生成树中第k长的边

注意定义类型,把ans数组定义成了int类型调了半个小时

/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int M = 510;
const int N = 1e6 + 4;
int n,k,fa[M],cnt,js;
double ans[M];
struct Pots{
    int x,y;
}p[N << 1];
struct node{
   int xx,yy;
   double dis;
}a[N<<1];
double calc(int x_1,int x_2,int y_1,int y_2){
   return sqrt((x_1 - x_2)*(x_1 - x_2) + (y_1 - y_2)*(y_1 - y_2));
}
int find(int x){
   if (fa[x] != x)fa[x] = find(fa[x]);
   return fa[x];
}
bool cmp(node a,node b){
    return a.dis < b.dis;
}
int main(){
   
   cin>>k>>n; 
   
   if(k > n){
   	  printf("0.00");
   	  return 0;
   }
   
   for(int i = 1;i <= n; i++) cin>>p[i].x>>p[i].y;
   for(int i = 1;i <= n; i++) fa[i] = i;   
   for(int i = 1;i <= n; i++)
     for(int j = i + 1;j <= n; j++){
     	  a[++cnt] = (node){i,j,calc(p[i].x,p[j].x,p[i].y,p[j].y)};
	 }

    sort(a + 1,a + cnt + 1,cmp);
  
    for(int i = 1; i <= cnt; i++){//构造最小生成树 
    	 int x = find(a[i].xx),y = find(a[i].yy);
    	 if(x != y){
    	 	fa[x] = y;
    	 	ans[++js] = a[i].dis; 
		 }
    }
	printf("%.2lf",ans[js - k + 1]);
}

10066. 「一本通 3.1 练习 1」新的开始

mind

最小生成树

由于每个矿井建修电站都有费用(可看作有权值的点),贪心优先选择权值最小的点,然后构建最小生成树,若两者连线消耗比要连接的点的权值小,更新这个要连的点的权值,同时ans记录答案

由于数据很水,所以用链接矩阵存图

/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
using namespace std;
const int M = 1e5;
const int inf = 0x3f3f3f3f;
int n,di[301],vis[301],minn,k,ans;
int js,head[301],e[301][301];

int main(){
   std::ios::sync_with_stdio(false);
   cin>>n;
   for(int i = 1;i <= n; i++)cin>>di[i];
   
   for(int i = 1;i <= n; i++)
   	  for(int j = 1,w;j <= n;j++){
   	       cin >>w;
		   e[i][j] = w; 
	   }
	
   for(int i = 1;i <= n; i++){
   	   minn = inf;
   	   
   	   for(int j = 1;j <= n; j++)
   	     if(!vis[j] && minn > di[j])
   	        minn = di[j],k = j;
			     	 
	    vis[k] = 1;ans += minn;
	    
	    for(int j = 1;j <= n; j++){
	      if(!vis[j] && di[j] > e[j][k])
		     di[j] = e[j][k];
		}
   }
   printf("%d",ans);
}

10067. 「一本通 3.1 练习 2」构造完全图

mind

完全图:有(n)个点,(frac{n*(n-1)}{2})条边构成的图

要求仅有一条最小生成树,所以加边的时候一定比当前边要小

(prim):把所有的点分为两个集合,(A),(B),起初(A)集合中只有一个点,然后从(B)每拿一个点就要添加

(cnt[x] * cnt[y] - 1)条边(x , y分别为两点的父亲节点(并查集)),所加的边至少要比当前的边大1.否则不满足仅有一个最小生成树的条件(模一下过程就好了)

/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int M = 1e5 + 2;
int n,fa[M],ans,cnt[M];
struct edge{
   int u,v,w;
}e[M];

int find(int x){
    if(fa[x]!=x)return fa[x]=find(fa[x]);
    else return fa[x];
}

bool cmp(edge a,edge b){return a.w<b.w;}

signed main(){
   std::ios::sync_with_stdio(false);
   cin >> n;
   for(int i = 1; i <=n - 1; i++){
   	   cin>>e[i].u>>e[i].v>>e[i].w;
   	   ans += e[i].w; 
   }
   for(int i = 1;i <= n; i++){  
       fa[i] = i;cnt[i] = 1;
   } 
   sort(e + 1,e + n,cmp);
   for(int i = 1; i < n; i++){
   	   int x = find(e[i].u),y = find(e[i].v);
   	   if(x != y){
		 ans += (cnt[x]*cnt[y] - 1)*(e[i].w + 1);
   	     fa[y] = x;
		 cnt[x] += cnt[y];	
	   }
   }
   printf("%lld",ans);
}

原文地址:https://www.cnblogs.com/Arielzz/p/14224737.html