NOIP 大活题。。吧
题意:给你一张n个点,m条边的图,求一棵有根生成树,使得除根节点外每个节点的不带权深度(根节点深度为1)与其父边的边权的乘积的和最小。
看起来是不是有点像最小生成树,那么大胆猜想一波。。??
很可惜,这不是一个猜测题。。(加上玄学模拟退火好像也可以乱搞过掉)
那么再看一下n小于等于12,爆搜或状压??
这里看看状压怎么写:
首先考虑状态,二进制表示点集就不说了,再看看题目,只涉及一个深度,那么把深度也看做状态就可以了。
因此设f[i][j]表示点集状态为i,最大深度为j时的最小代价。
那么转移的话,设x为i的子集,则f[i][j]=min (f[i][j], f[x][j-1]+cost*j)。
那么现在题就是解决cost了。
考虑到cost实际上就是所有属于i但不属于x的点,向x连边后构成i的最小边权和。
所以直接在转移时计算就好了。
至此,这个问题基本已经解决了。
小优化:可以提前预处理每个状态能向外连的点集。
#include <bits/stdc++.h> using namespace std; #define INF 0x3f3f3f3f #define Min(a,b) a>b?b:a const int N=13; const int Mx=4100; int n,m,All,Ans,dis[N][N],g[Mx],f[Mx][N]; inline void New_ () { memset (f, 0x3f, sizeof (f) ); memset (dis, 0x3f, sizeof (dis) ); } int main () { std::ios::sync_with_stdio (false); New_ (); cin >> n >> m; for (int i=1, x, y, z;i<=m;++i) { cin >> x >> y >> z; x--, y--; //方便状态转移 dis[x][y]=dis[y][x]=Min (dis[x][y], z); } int All=1<<n; for (int i=1;i<All;++i) for (int j=0;j<n;++j) if ( ( (1<<j) | i) ==i) { dis[j][j]=0; for (int k=0;k<n;++k) if (dis[j][k]!=INF) g[i]|= (1<<k); } for (int i=0;i<n;++i) f[1<<i][0]=0; for (int i=2;i<All;++i) for (int s=i-1;s;s= (s-1) &i) if ( (g[s] | i) ==g[s]) { // s必须能转移到i int Cost=0, ss=s^i, pay; for (int k=0;k<n;++k) { if ( ( (1<<k) & ss) !=0) { pay=INF; for (int j=0;j<n;++j) if ( ( (1<<j) & s) !=0) pay=Min (pay, dis[j][k]); Cost+=pay; } } for (int j=1;j<n;++j) if (f[s][j-1]!=INF) f[i][j]=min (f[i][j], f[s][j-1]+Cost*j); } Ans=INF; for (int i=0;i<n;++i) Ans=min (Ans, f[All-1][i]); cout << Ans << endl; return 0; }