题目链接:https://www.luogu.org/problem/P3959
很显然开通的一定是树。
所以可以变相的认为赞助商搞的起点是树的根,每个结点到树的根的距离就是这个点的层数。
有点像树形dp的状压dp吧。枚举根节点,然后枚举每一层的结点的集合,算出这一集合的最小代价,存到f[floor][s]中,表示已经选了的(注意啊不是这一层选了的)结点的集合为s,选到了floor层的最小代价。
所以在递归的每一层中,枚举将要选的集合,依次判断集合中的点能否加入以及所有点都加入后的最小代价。用来更新这一层f的最小值。
PS:学到了一个技巧
枚举一个集合里面所以子集的方法:
for(int ns=mask;ns>0;ns=(ns-1)&mask)//ns就是mask的所有子集
-1相当于去掉一个集合里面最后一个1,并且将他后面的所有0改成1。这样的话最可以从大到小枚举一个集合的所有子集。
#include <bits/stdc++.h>
using namespace std;
/*
状压的时候从0开始的好处:位移的时候不用写1<<(i-1)而是1<<i
*/
const int Inf=1e9;
int n,m,f[15][5005],dis[15][15],vis[15][5005];
int find(int floor,int s)
{
if(floor>n) return 1e9;
if(s==(1<<n)-1) return 0;
if(vis[floor][s]) return f[floor][s];
vis[floor][s]=1;
f[floor][s]=1e9;
int mask=(1<<n)-1-s;//没有被选的集合
for(int ns=mask;ns>0;ns=(ns-1)&mask)//ns就是mask的所有子集
//这里就是枚举哪些不在集合外的点在这层可以塞到集合里面(最终形成的一定是一棵树啊)
{
int cur=0;//把ns里的点都塞进去的总代价
for(int i=0;i<n;i++)
{
if((1<<i)&ns)
{
int c=Inf;
for(int j=0;j<n;j++)
{
if(((1<<j)&s)&&dis[i][j]!=Inf)
{
c=min(c,dis[i][j]*floor);//这里因为是一棵树,floor就是离根节点的距离
}
}
if(c==Inf) {cur=Inf;break;}
cur+=c;
}
}
if(cur==Inf) continue;
f[floor][s]=min(f[floor][s],find(floor+1,ns+s)+cur);
}
return f[floor][s];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
dis[i][j]=1e9;
for(int i=1;i<=m;i++)
{
int x,y,z;scanf("%d%d%d",&x,&y,&z);
x--;y--;dis[x][y]=min(dis[x][y],z);
dis[y][x]=min(dis[y][x],z);
}
int minv=1e9;
for(int i=0;i<n;i++)//i作为起始点
{
minv=min(minv,find(1,1<<i));
}
printf("%d",minv);
return 0;
}