Loading

题解 【AT2230 Water Distribution】

\(\huge\texttt{AT2230}\)

模拟赛出这题,结束后恍然大悟,充分证明我的垃圾。

状压 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;
}
posted @ 2021-03-02 21:00  RedreamMer  阅读(80)  评论(0编辑  收藏  举报