@darkproject
2018-02-15T23:14:32.000000Z
字数 5309
阅读 836
2018寒假集训
次小生成树:
基本的思想就是连入一条不在最小生成树上的边,从而形成一个环,去掉在环中并且在最小生成树上最大的边,遍历所有不在最小生成树上的边并进行同样的操作最小值即为次小生成树,简单证明就是连入一条边后去掉一个最大值相当于比原来的值增加的值最小(增加量=添加的边-环上的某一条边(并且这条边在最小生成树上),添加的边的权值一定,因此使环上的边最大)
A - Qin Shi Huang's National Road System (UVALive - 5713)
n个城市,使其任意连通。我们可以选择一条路不消耗劳动力(这里可以等价于距离0的路)。输入给予每2个城市的坐标和人口,我们要使法术路所连接的人口数量A尽量大,所有连接路的劳动力B尽量小,求A/B的最大值。贪心做法显然不可以。
首先使所有连接路劳动力最小,求出最小生成树.然后枚举法术路,枚举出的法术路有2种情况,在原最小生成树上,在最小生成树外。对于在最小生成树外的情况,我们加入一条0权边,使其有环,我们需要减去在最小生成树上且在环上的最长边(即最小瓶颈路:2点间最长边最短的路径,最小生成树上的最长边即为最小瓶颈路)这样来保证B尽量小。这同时也是次小生成树的思想。对于在最小生成树上的情况,我们直接令此边边权为0即可。
复杂度O(mlogn+)
生成树一般均为无向图,有向的话朱刘。
补充cayley定理:n个结点的完全图中,可以产生个生成树
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 1005;
const int inf =0x3f3f3f3f;
double map[maxn][maxn],path[maxn][maxn];
bool used[maxn][maxn];
int pre[maxn];
double dis[maxn];
int t,n,x[maxn],y[maxn],p[maxn];
void init()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(i==j) map[i][j]=0;
else map[i][j]=inf;
}
memset(used,0,sizeof(used));
memset(path,0,sizeof(path));
}
double prim()
{
double ans=0;
bool vis[maxn];
memset(vis,0,sizeof(vis));
memset(path,0,sizeof(path));
for(int i=1;i<=n;i++)
{
dis[i]=map[1][i];
pre[i]=1;
}
dis[1]=0,vis[1]=1;
for(int i=2;i<=n;i++)
{
double minn=inf;
int k;
for(int j=1;j<=n;j++)
{
if(!vis[j]&&minn>dis[j])
{
minn=dis[j];
k=j;
}
}
ans+=minn;
vis[k]=1;
used[k][pre[k]]=used[pre[k]][k]=1;
//对最小生成树上的边进行标记
for(int j=1;j<=n;j++)
{
if(vis[j]&&j!=k) path[j][k]=path[k][j]=max(path[j][pre[k]],dis[k]);//求2点的最小瓶颈路同时也是最小生成树上2点之间的最长边
if(!vis[j]&&dis[j]>map[k][j])
{
dis[j]=map[k][j];
pre[j]=k;
}
}
}
return ans;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
init();
for(int i=1;i<=n;i++)
scanf("%d%d%d",&x[i],&y[i],&p[i]);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
int xx=x[i]-x[j];
int yy=y[i]-y[j];
double d=sqrt((double)xx*xx+yy*yy);
map[i][j]=map[j][i]=d;
}
double ans=prim();
double rel=-1;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
double num=p[i]+p[j];
if(!used[i][j])
{
double tmp=ans-path[i][j];
rel=max(rel,num/tmp);
}
else if(used[i][j])
{
int xx=x[i]-x[j];
int yy=y[i]-y[j];
double d=sqrt((double)xx*xx+yy*yy);
double tmp=ans-d;
rel=max(rel,num/tmp);
}
}
printf("%.2f\n",rel);
}
return 0;
}
D - Slim Span (UVALive - 3887)
含n个结点的无向图,求其所有生成树中最大边减去最小边的值中的最小值。暴力枚举区间[l,r]kruskal求所有生成树。复杂度O()
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn =105;
const int inf = 0x3f3f3f3f;
int par[maxn];
int n,m;
struct mxr
{
int from,to,w;
bool operator <(const mxr a)const{
return w<a.w;
}
}edge[maxn*maxn];
void init()
{
for(int i=1;i<=n;i++)
par[i]=i;
}
int find(int x)
{
if(x==par[x])
return x;
else
return par[x]=find(par[x]);
}
int main()
{
while(scanf("%d%d",&n,&m))
{
if(n==0&&m==0) break;
for(int i=1;i<=m;i++)
scanf("%d%d%d",&edge[i].from,&edge[i].to,&edge[i].w);
sort(edge+1,edge+m+1);
int ans=inf;
for(int l=1;l<=m;l++)
{
int cnt=0;
init();
for(int r=l;r<=m;r++)
{
int x=find(edge[r].from);
int y=find(edge[r].to);
if(x!=y){
par[x]=y;
cnt++;
if(cnt==n-1){
ans=min(ans,edge[r].w-edge[l].w);
break;
}
}
}
}
if(ans==inf) printf("-1\n");
else printf("%d\n",ans);
}
return 0;
}
E - ACM Contest and Blackout (UVA - 10600)
无向图,使其连通即求生成树。要求给出花费最小和第二小的方案,即求最小生成树和次小生成树,输出即可裸。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 105;
const int inf = 0x3f3f3f3f;
int map[maxn][maxn],dis[maxn],path[maxn][maxn];
int n,m,q,pre[maxn],t;
bool used[maxn][maxn];
void init()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(i==j) map[i][j]=0;
else map[i][j]=inf;
}
memset(used,0,sizeof(used));
memset(path,0,sizeof(path));
}
int prim()
{
int ans=0;
bool vis[maxn];
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
{
dis[i]=map[1][i];
pre[i]=1;
}
dis[1]=0,vis[1]=1;
for(int i=2;i<=n;i++)
{
int minn=inf;
int k;
for(int j=1;j<=n;j++)
{
if(!vis[j]&&minn>dis[j])
{
minn=dis[j];
k=j;
}
}
ans+=minn;
vis[k]=1;
used[k][pre[k]]=used[pre[k]][k]=1;
for(int j=1;j<=n;j++)
{
if(vis[j]&&j!=k) path[j][k]=path[k][j]=max(path[j][pre[k]],dis[k]);
if(!vis[j]&&dis[j]>map[k][j]){
dis[j]=map[k][j];
pre[j]=k;
}
}
}
return ans;
}
int main()
{
int mxr,hyx;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
init();
int a,b,c;
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&a,&b,&c);
map[a][b]=map[b][a]=c;
}
mxr=prim();
hyx=inf;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
if(!used[i][j])
hyx=min(hyx,mxr+map[i][j]-path[i][j]);
}
printf("%d %d\n",mxr,hyx);
// printf("\n");
}
return 0;
}
G - Arctic Network (UVA - 10369)
有P个地点需要进行通信有2种方式卫星和无线电,卫星交互无距离限制,而无线电有距离限制要求为D,其中有S个地点可以进行卫星交互。要使所有地点均通信成功,且D值尽量小。输出D值。我们求一遍最小生成树,将边排序即可,因为其中有s-1条边不受距离限制,我们排除这S-1条边后下一条边的边权即是我们要求的D值。即排序后的第p-s条边为我们所要的解。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn = 505;
const int inf =0x3f3f3f3f;
int n,s,p,pre[maxn];
double dis[maxn],map[maxn][maxn];
int x[maxn],y[maxn];
double mxr[maxn];
void init()
{
for(int i=1;i<=p;i++)
for(int j=1;j<=p;j++)
if(j==i) map[i][j]=0;
else map[i][j]=inf;
}
int prim()
{
int cnt=0;
bool vis[maxn];
memset(vis,0,sizeof(vis));
for(int i=1;i<=p;i++)
{
dis[i]=map[1][i];
pre[i]=1;
}
vis[1]=1,dis[1]=0;
for(int i=2;i<=p;i++)
{
double minn=inf;
int k;
for(int j=1;j<=p;j++)
{
if(!vis[j]&&minn>dis[j])
{
minn=dis[j];
k=j;
}
}
vis[k]=1;
mxr[cnt++]=map[k][pre[k]];
for(int j=1;j<=p;j++)
{
if(!vis[j]&&dis[j]>map[k][j])
{
dis[j]=map[k][j];
pre[j]=k;
}
}
}
return cnt;
}
int main()
{
scanf("%d",&n);
while(n--)
{
scanf("%d%d",&s,&p);
for(int i=1;i<=p;i++)
scanf("%d%d",&x[i],&y[i]);
for(int i=1;i<=p;i++)
for(int j=i+1;j<=p;j++)
{
int xx=x[i]-x[j];
int yy=y[i]-y[j];
double d=sqrt((double)xx*xx+yy*yy);
map[i][j]=map[j][i]=d;
}
int m=prim();
sort(mxr,mxr+m);
printf("%.2f\n",mxr[p-s-1]);
}
return 0;
}