野餐计划
题目
大佬讲解
这个是算法进阶指南上面的题目,ACwing上面的秦大佬讲的很详细
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
#define quick() ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)//读入优化
#define mem(a,b) memset(a,b,sizeof(a))//初始化
#define init() mem(g,0),mem(vis,false),root=cnt=tot2=tot=0,q.clear(),q2.clear()//初始化大军
#define add(a,b,c) g[a][b]=g[b][a]=c//无向图加边
#define mk(a,b) make_pair(a,b)//简便
int tot2,tot,n,m,fa[N],cnt,c,s,root,g[31][31],dis[31][31];
//tot2为边的数量
//tot为点的数量
//cnt为编号的数量
map<string,int> q;//映射关系
map<pair<int,int>,bool> q2;//统计这条边是否出现在最小生成树中
int vis[N];//标记连通块的编号
string a,b;
int find(int x)
{
return x==fa[x]?fa[x]:fa[x]=find(fa[x]);//并查集找红太阳
}
struct edge1
{
int x,y,w;
} g2[N];
int cmp (edge1 a,edge1 b)
{
return a.w<b.w;//最小边排序
}
struct node
{
int u,v,d;//(u,v)节点权值为d
inline void inits()
{
u=v=0;
d=-1;
}
} dp[N];
struct edge2
{
inline void add_edge(int a,int b,int c)//加入一条边
{
g2[++tot2].x=a;//起点
g2[tot2].y=b;//终点
g2[tot2].w=c;//权值
}
inline int kruskal()
{
sort(g2+1,g2+1+tot2,cmp);//排序,找最小
int ans=0;//我们的目标连通块的连通块编号
for(int i=1; i<=tot2; i++)
{
int x=find(g2[i].x),y=find(g2[i].y);//求出所在连通块
if (x==1 || y==1 || x==y)//不是目标连通块,或者已经在一起了
continue;
fa[x]=y;//合并
ans+=g2[i].w;//统计
q2[mk(g2[i].x,g2[i].y)]=true;//这条边出现过
q2[mk(g2[i].y,g2[i].x)]=true;
}
return ans;
}
} g3;
void read()
{
quick();
init();
cin>>n;
root=q["Park"]=tot=1;//Park节点就是我们的一号节点
for(int i=1; i<=n; i++)
{
cin>>a>>b>>c;
if (!q[a])//名字读入
q[a]=(++tot);//新编号
if (!q[b])
q[b]=(++tot);//新编号
g3.add_edge(q[a],q[b],c);//加边
add(q[a],q[b],c);//加边
fa[i]=i;//初始化每个点的父亲节点
}
cin>>s;
}
void dfs(int x)
{
for(int j=2; j<=tot; j++)
if (g[x][j] && !vis[j])//有边,但是木有被标记
{
vis[j]=cnt;
dfs(j);
}
}
void pd()//连通块划分
{
for(int i=2; i<=tot; i++)
if (!vis[i])
{
cnt++;//又来一个
vis[i]=cnt;
dfs(i);
}
}
void dfs(int now,int last)//计算(1,x)路径上最大边
{
for(int i=2; i<=tot; i++)
{
if(i==last || !q2[mk(now,i)])//点重叠,或者没有这条边
continue;
if(dp[i].d==-1)//没有来过
{
if(dp[now].d>g[now][i])
dp[i]=dp[now];
else
{
dp[i].u=now;
dp[i].v=i;
dp[i].d=g[now][i];
}
}
dfs(i,now);
}
}
void work()
{
pd();
int ans=g3.kruskal();//统计每一个连通块的值
for(int i=1; i<=cnt; i++)
{
int now=0x3f3f3f3f,st=0;//初始值为INF
for(int j=2; j<=tot; j++)
if (vis[j]==i)//属于这个连通块
if (now>g[1][j] && g[1][j]!=0)
{
now=g[1][j];//找到与1相连最小的边
st=j;
}
ans+=now;//将每一个连通块与1相连
q2[mk(1,st)]=q2[mk(st,1)]=true;
}
int t=cnt;
while(s>t)
{
s--;
int now=0,idx=0;
for(int i=1; i<=1100; i++)
dp[i].inits();
dfs(1,-1);//求每一个点到1的途中最大边是谁?
// 这个dp我看了好久才发现可能表示到i点路径最大权值
for(int j=2; j<=tot; j++)
{
if(now<dp[j].d-g[1][j] && g[1][j])
{
now=dp[j].d-g[1][j];//找到最大权值边
idx=j;
}
}
if (now<=0)//已经不会多优秀了
break;
ans=ans-now;
q2[mk(dp[idx].u,dp[idx].v)]=false;//删除边
q2[mk(1,idx)]=q2[mk(idx,1)]=true;//添加边
}
cout<<"Total miles driven: "<<ans;
}
int main()
{
read();
work();
return 0;
}
作者:秦淮岸灯火阑珊
链接:https://www.acwing.com/solution/acwing/content/2488/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
每次做题提醒自己:题目到底有没有读懂,有没有分析彻底、算法够不够贪心、暴力够不够优雅。