题解 【AT2230 Water Distribution】
模拟赛出这题,结束后恍然大悟,充分证明我的垃圾。
状压 DP 好题。
思路
(以下“集合的权值"意义均为题目中所求的最大值)
发现题目中给定的 \(N\) 很小,考虑搜索或状压。
尝试使用状压。
对于一个集合,它之中所有节点的剩余水量相同,权值就是其所有节点剩余水量的最大值,还有可能由多个不相交子集的构成,权值就是多个子集权值的最小值。
对于某个集合的所有结点的剩余水量都相等,考虑如何流通传递水量。
-
结论 1 :流通传递的水量经过的边构成的一定是一颗树。
证明:首先必然是一整个连通块,若不是,则会被第二种情况考虑,其次,若边构成了一个环,则肯定能隐去一条最大边,剩余的的传递关系仍然成立。
-
结论 2 :每条树边最多被经过一次
证明:类似于网络流的反向边,如果一条边正反经过多次,是可以抵消一部分的,而多次同方向经过可以叠加。
用 \(dp[i]\) 表示集合状态为 \(i\) 最大的值。
计算两种情况的贡献,通过结论1、2,可知第一种值为 \(\frac{suma_i-MST}{V}\) ( \(V\) 是点数,\(MST\) 是集合最小生成树边权值和),第二种通过枚举子集得到 \(dp[i]=\max (dp[j],dp[i\oplus j])\)。
预处理所有集合的最小生成树和第一种情况的值,时间复杂度 \(O(2^nn^2)\) ,第二种情况更新 DP 通过枚举子集时间复杂度 \(O(3^n)\)。
注意精度,好像不太卡。
代码
#include <bits/stdc++.h>
using namespace std;
#define LD long double
const int N = 15;
inline int read()
{
int s = 0;
register bool neg = 0;
register char c = getchar();
for (; c < '0' || c > '9'; c = getchar())
neg |= (c == '-');
for (; c >= '0' && c <= '9'; s = s * 10 + (c ^ 48), c = getchar())
;
s = (neg ? -s : s);
return s;
}
int a;
LD x[N + 1], y[N + 1], s[N + 1], dis[N + 1], val[(1 << N)], valt[1 << N], sum[1 << N], dp[1 << N];
bool vis[N + 1], vist[N + 1];
inline LD dist(LD _x, LD _y, LD xx, LD yy) { return sqrt((_x - xx) * (_x - xx) + (_y - yy) * (_y - yy)); }
inline LD prim(int n)
{
for (int i = 0; i <= a; i++)
dis[i] = 1e100;
memset(vist, 0, sizeof(vist));
for (int i = 1; i <= a; i++)
if (vis[i])
{
dis[i] = 0;
break;
}
for (int i = 1; i < n; i++)
{
int mn = 0;
for (int j = 1; j <= a; j++)
if (vis[j] && !vist[j] && dis[j] < dis[mn])
mn = j;
vist[mn] = 1;
for (int j = 1; j <= a; j++)
if (vis[j] && !vist[j])
{
LD tmp = dist(x[mn], y[mn], x[j], y[j]);
if (dis[j] > tmp)
dis[j] = tmp;
}
}
LD res = 0;
for (int i = 1; i <= a; i++)
if (vis[i])
res += dis[i];
return res;
}
signed main()
{
a = read();
const int mx = (1 << a);
for (int i = 1; i <= a; i++)
scanf("%Lf %Lf %Lf", &x[i], &y[i], &s[i]);
for (int i = 1; i < mx; i++)
{
memset(vis, 0, sizeof(vis));
for (int j = 1; j <= a; j++)
if (i & (1 << (j - 1)))
vis[j] = 1, sum[i]++, valt[i] += s[j];
val[i] = prim(sum[i]);
}
for (int i = 1; i < mx; i++)
{
dp[i] = (valt[i] - val[i]) / sum[i];
for (int j = (i & (i - 1)); j; j = ((j - 1) & i))
dp[i] = max(dp[i], min(dp[j], dp[i ^ j]));
}
printf("%.10Lf", dp[mx - 1]);
return 0;
}