Atcoder CODE FESTIVAL 2016 Grand Final E - Water Distribution
Atcoder CODE FESTIVAL 2016 Grand Final E - Water Distribution
题目链接:https://atcoder.jp/contests/cf16-exhibition-final/tasks/cf16_exhibition_final_e
洛谷链接:https://www.luogu.com.cn/problem/AT2230
模拟赛出了这道题,赛时觉得这题是最简单的一题,想了很久但是第一个结论就想错了,知道是状压但始终设不出来状态。
Solution
我们思考答案是怎样得到的。显然由一些连通块组成,那么思路就是分别考虑每个连通块的最优解,然后\(O(3^n)\)枚举子集状压合并答案。
想到这一步之后就很简单了。对于一个连通块,我们可以求出其最小生成树,假设最小生成树的各边权之和为\(E\),连通块内水的总量为\(A\),连通块的点数为\(V\),那么这个连通块的答案就是\(\frac{A-E}{V}\)。
连通块的最小生成树可以\(O(2^n n^2)\text{DP}\)求出。于是总复杂度为\(O(2^n n^2 + 3^n)\)。
点我看代码✈
#include <cstdio>
#include <iostream>
#include <cmath>
#define db double
using namespace std;
inline void read(int &x) {
x = 0; int f = 0; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
if(f) x = ~x + 1;
}
const int N = 16;
const db inf = 1e18;
int n;
struct City {
db x, y, a;
}p[N];
db dis[N][N];
inline db sqr(db x) {return x * x;}
db f[1 << N], g[1 << N];
inline db popcount(int x) {int res = 0; while(x) ++res, x &= x - 1; return res;}
inline void update(db &x, db y) {x = min(x, y);}
int main() {
freopen("water.in","r",stdin);
freopen("water.out","w",stdout);
scanf("%d",&n);
for(int i = 1; i <= n; ++i) scanf("%lf%lf%lf",&p[i].x,&p[i].y,&p[i].a);
for(int i = 1; i <= n; ++i)
for(int j = i + 1; j <= n; ++j)
dis[i][j] = dis[j][i] = sqrt(sqr(p[i].x - p[j].x) + sqr(p[i].y - p[j].y));
for(int i = 0; i < 1 << n; ++i) f[i] = inf;
for(int i = 1; i <= n; ++i) f[1 << i - 1] = 0;
for(int s = 1; s < 1 << n; ++s)
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
if((s & (1 << i - 1)) && !(s & (1 << j - 1))) update(f[s | (1 << j - 1)], f[s] + dis[i][j]);
for(int s = 1; s < 1 << n; ++s) {
for(int i = 1; i <= n; ++i) if(s & (1 << i - 1)) g[s] += p[i].a;
g[s] -= f[s], g[s] /= popcount(s);
for(int t = (s - 1) & s; t; t = (t - 1) & s)
g[s] = max(g[s], min(g[t], g[s ^ t]));
}
printf("%.12lf\n",g[(1 << n) - 1]);
}