pku 1639 野餐计划(最小限度生成树)
很久以前就打算写一下的,无奈关于最小限度生成树的论文是在看不下去,觉得实现起来很复杂,昨天晚上在hyh的要求下坚持看完了论文并实现了出来。
题目大意是:
矮人虽小却喜欢乘坐巨大的轿车,轿车大到可以装下无论多少矮人。某天,N(N≤5000)个矮人打算到野外聚餐。为了集中到聚餐地点,矮人A要么开车到矮人B家中,留下自己的轿车在矮人B家,然后乘坐B的轿车同行;要么直接开车到聚餐地点,并将车停放在聚餐地。
虽然矮人的家很大,可以停放无数量轿车,但是聚餐地点却最多只能停放K辆轿车。现在给你一张加权无向图,它描述了N个矮人的家和聚餐地点,要你求出所有矮人开车的最短总路程。
[输入文件]
第一行是整数M(M<=49000),接下来M行描述了M条道路。每行形式如同:S1 S2 x,S1和S2均是大于0小于5000的整数,x小于等于1000。最后一行包含两个整数k,root。root表示聚餐的地点。
[输出文件]
仅一行,形式如同:Total miles driven: xxxXxx是整数,表示最短总路程。
[输入输出实例]
Picnic.in Picnic.out
5 1 2 1 2 3 1 3 4 1 4 5 1 5 6 1 1 1 Total miles driven: 5
my code:
题目大意是:
矮人虽小却喜欢乘坐巨大的轿车,轿车大到可以装下无论多少矮人。某天,N(N≤5000)个矮人打算到野外聚餐。为了集中到聚餐地点,矮人A要么开车到矮人B家中,留下自己的轿车在矮人B家,然后乘坐B的轿车同行;要么直接开车到聚餐地点,并将车停放在聚餐地。
虽然矮人的家很大,可以停放无数量轿车,但是聚餐地点却最多只能停放K辆轿车。现在给你一张加权无向图,它描述了N个矮人的家和聚餐地点,要你求出所有矮人开车的最短总路程。
[输入文件]
第一行是整数M(M<=49000),接下来M行描述了M条道路。每行形式如同:S1 S2 x,S1和S2均是大于0小于5000的整数,x小于等于1000。最后一行包含两个整数k,root。root表示聚餐的地点。
[输出文件]
仅一行,形式如同:Total miles driven: xxxXxx是整数,表示最短总路程。
[输入输出实例]
Picnic.in Picnic.out
5 1 2 1 2 3 1 3 4 1 4 5 1 5 6 1 1 1 Total miles driven: 5
my code:
#include <cstdio>
#include <iostream>
#include <string>
#include <map>
using namespace std;
const int maxn=50;
int G[maxn][maxn],G2[maxn][maxn]; //G原图 G2去掉限度节点后的图
int label[maxn],tot[maxn]; //label对G2进行编号,统一连同分量的节点标号相同;tot[i]表示标号为i的节点个数
bool u[maxn],G3[maxn][maxn],b[maxn],u2[maxn]; //u:dfs编号时的标记;G3:最小生成树
int k,root,m,minid,maxid,id,maxdel,me1,me2; //id表示当前强连同分量的标号值
map<string,int> namedic;
inline int min(int a,int b) { return a<b?a:b; }
inline int max(int a,int b) { return a>b?a:b; }
//读入数据
void input() {
int x;
string s1,s2;
namedic["Park"]=1;
minid=1;root=1;
maxid=1;
memset(G,0,sizeof G);
scanf("%d",&m);
for(int i=0;i<m;i++) {
cin>>s1>>s2>>x;
if(namedic[s1]==0) namedic[s1]=(++maxid);
if(namedic[s2]==0) namedic[s2]=(++maxid);
G[namedic[s1]][namedic[s2]]=G[namedic[s2]][namedic[s1]]=x;
}
scanf("%d",&k);
}
//dfs求强连同分量并标号
void dfs(int v) {
u[v]=true;
tot[id]++;
label[v]=id;
for(int i=minid;i<=maxid;i++)
if(G2[v][i]>0&&!u[i]) {
dfs(i);
}
}
//计算从root->v出发的圈中除root<->v外的边的最大值maxdel
//me1,me2记录最大边的端点
bool circlemax(int v,int j) {
u2[v]=true;
for(int i=minid;i<=maxid;i++) {
if(!G3[v][i]) continue;
if(u2[i]) continue;
if(i==root) {
maxdel=j;
return true;
}
if(j<G[v][i]) {
me1=v;me2=i;
}
if(circlemax(i,max(j,G[v][i]))) return true;
}
return false;
}
//对k限度生成树进行一次换边操作
int addmstedge() {
int bestdel=-1,bestret=999999,me1b,me2b;
int addv;
for(int i=minid;i<=maxid;i++) {
if(G[root][i]==0) continue;
if(G3[root][i]==true) continue;
memset(u2,false,sizeof u2);
circlemax(i,0);
//printf("%d\n",maxdel);
if(G[root][i]-maxdel<bestret) {
bestret=G[root][i]-maxdel;
addv=i;
me1b=me1,me2b=me2;
}
}
//printf("%d\n",bestret);
G3[root][addv]=G3[addv][root]=true;
G3[me1b][me2b]=G3[me2b][me1b]=false;
//printf("%d %d %d\n",addv,me1b,me2b);
if(bestret==999999) return 0;else return bestret;
}
//求连同分量并标号
void make_cc() {
for(int i=minid;i<=maxid;i++) {
for(int j=minid;j<=maxid;j++) {
G2[i][j]=G[i][j];
if(i==root||j==root) G2[i][j]=0;
}
}
memset(u,false,sizeof u);
memset(tot,0,sizeof tot);
id=0;u[root]=true;
for(int i=minid;i<=maxid;i++) {
if(i!=root)
if(!u[i]) {
id++;
dfs(i);
}
}
}
//计算标号为id的连同分量的最小生成树
int calc_mst(int id) {
int sp,ep,sum=0;
for(int i=minid;i<=maxid;i++) {
if(label[i]==id) {
b[i]=true;
break;
}
}
for(int i=1;i<tot[id];i++) {
int min=999999;
for(int j=minid;j<=maxid;j++) {
if(label[j]!=id) continue;
if(!b[j]) continue;
for(int kk=minid;kk<=maxid;kk++) {
if(label[kk]!=id) continue;
if(b[kk]) continue;
if(G2[j][kk]==0) continue;
if(G2[j][kk]<min) {
min=G2[j][kk];
sp=j;
ep=kk;
}
}
}
sum+=min;
b[ep]=true;
G3[sp][ep]=G3[ep][sp]=true;
}
return sum;
}
void solve() {
int sum=0;
make_cc();
memset(b,false,sizeof b);
memset(G3,false,sizeof G3);
for(int i=1;i<=id;i++)
sum+=calc_mst(i);
for(int i=1;i<=id;i++) {
int min=999999;
int nowid;
for(int j=minid;j<=maxid;j++) {
if(label[j]!=i) continue;
if(G[root][j]==0) continue;
if(G[root][j]<min) {
min=G[root][j];
nowid=j;
}
}
sum+=min;
G3[root][nowid]=G3[nowid][root]=true;
}
//for(int i=minid;i<=maxid;i++)
// for(int j=i;j<=maxid;j++)
// if(G3[i][j]) printf("%d %d\n",i,j);
//printf("%d\n",sum);
int bestans=sum,ret=bestans;
for(int nowx=id;nowx<k;nowx++) {
bestans=bestans+addmstedge();
ret=min(bestans,ret);
}
printf("Total miles driven: %d\n",ret);
}
int main() {
input();
solve();
return 0;
}
#include <iostream>
#include <string>
#include <map>
using namespace std;
const int maxn=50;
int G[maxn][maxn],G2[maxn][maxn]; //G原图 G2去掉限度节点后的图
int label[maxn],tot[maxn]; //label对G2进行编号,统一连同分量的节点标号相同;tot[i]表示标号为i的节点个数
bool u[maxn],G3[maxn][maxn],b[maxn],u2[maxn]; //u:dfs编号时的标记;G3:最小生成树
int k,root,m,minid,maxid,id,maxdel,me1,me2; //id表示当前强连同分量的标号值
map<string,int> namedic;
inline int min(int a,int b) { return a<b?a:b; }
inline int max(int a,int b) { return a>b?a:b; }
//读入数据
void input() {
int x;
string s1,s2;
namedic["Park"]=1;
minid=1;root=1;
maxid=1;
memset(G,0,sizeof G);
scanf("%d",&m);
for(int i=0;i<m;i++) {
cin>>s1>>s2>>x;
if(namedic[s1]==0) namedic[s1]=(++maxid);
if(namedic[s2]==0) namedic[s2]=(++maxid);
G[namedic[s1]][namedic[s2]]=G[namedic[s2]][namedic[s1]]=x;
}
scanf("%d",&k);
}
//dfs求强连同分量并标号
void dfs(int v) {
u[v]=true;
tot[id]++;
label[v]=id;
for(int i=minid;i<=maxid;i++)
if(G2[v][i]>0&&!u[i]) {
dfs(i);
}
}
//计算从root->v出发的圈中除root<->v外的边的最大值maxdel
//me1,me2记录最大边的端点
bool circlemax(int v,int j) {
u2[v]=true;
for(int i=minid;i<=maxid;i++) {
if(!G3[v][i]) continue;
if(u2[i]) continue;
if(i==root) {
maxdel=j;
return true;
}
if(j<G[v][i]) {
me1=v;me2=i;
}
if(circlemax(i,max(j,G[v][i]))) return true;
}
return false;
}
//对k限度生成树进行一次换边操作
int addmstedge() {
int bestdel=-1,bestret=999999,me1b,me2b;
int addv;
for(int i=minid;i<=maxid;i++) {
if(G[root][i]==0) continue;
if(G3[root][i]==true) continue;
memset(u2,false,sizeof u2);
circlemax(i,0);
//printf("%d\n",maxdel);
if(G[root][i]-maxdel<bestret) {
bestret=G[root][i]-maxdel;
addv=i;
me1b=me1,me2b=me2;
}
}
//printf("%d\n",bestret);
G3[root][addv]=G3[addv][root]=true;
G3[me1b][me2b]=G3[me2b][me1b]=false;
//printf("%d %d %d\n",addv,me1b,me2b);
if(bestret==999999) return 0;else return bestret;
}
//求连同分量并标号
void make_cc() {
for(int i=minid;i<=maxid;i++) {
for(int j=minid;j<=maxid;j++) {
G2[i][j]=G[i][j];
if(i==root||j==root) G2[i][j]=0;
}
}
memset(u,false,sizeof u);
memset(tot,0,sizeof tot);
id=0;u[root]=true;
for(int i=minid;i<=maxid;i++) {
if(i!=root)
if(!u[i]) {
id++;
dfs(i);
}
}
}
//计算标号为id的连同分量的最小生成树
int calc_mst(int id) {
int sp,ep,sum=0;
for(int i=minid;i<=maxid;i++) {
if(label[i]==id) {
b[i]=true;
break;
}
}
for(int i=1;i<tot[id];i++) {
int min=999999;
for(int j=minid;j<=maxid;j++) {
if(label[j]!=id) continue;
if(!b[j]) continue;
for(int kk=minid;kk<=maxid;kk++) {
if(label[kk]!=id) continue;
if(b[kk]) continue;
if(G2[j][kk]==0) continue;
if(G2[j][kk]<min) {
min=G2[j][kk];
sp=j;
ep=kk;
}
}
}
sum+=min;
b[ep]=true;
G3[sp][ep]=G3[ep][sp]=true;
}
return sum;
}
void solve() {
int sum=0;
make_cc();
memset(b,false,sizeof b);
memset(G3,false,sizeof G3);
for(int i=1;i<=id;i++)
sum+=calc_mst(i);
for(int i=1;i<=id;i++) {
int min=999999;
int nowid;
for(int j=minid;j<=maxid;j++) {
if(label[j]!=i) continue;
if(G[root][j]==0) continue;
if(G[root][j]<min) {
min=G[root][j];
nowid=j;
}
}
sum+=min;
G3[root][nowid]=G3[nowid][root]=true;
}
//for(int i=minid;i<=maxid;i++)
// for(int j=i;j<=maxid;j++)
// if(G3[i][j]) printf("%d %d\n",i,j);
//printf("%d\n",sum);
int bestans=sum,ret=bestans;
for(int nowx=id;nowx<k;nowx++) {
bestans=bestans+addmstedge();
ret=min(bestans,ret);
}
printf("Total miles driven: %d\n",ret);
}
int main() {
input();
solve();
return 0;
}