zoukankan      html  css  js  c++  java
  • 题解 AT2230【Water Distribution】

    (huge exttt{AT2230})

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

    状压 DP 好题。

    思路

    (以下“集合的权值"意义均为题目中所求的最大值)

    发现题目中给定的 (N) 很小,考虑搜索或状压。

    尝试使用状压。

    对于一个集合,它之中所有节点的剩余水量相同,权值就是其所有节点剩余水量的最大值,还有可能由多个不相交子集的构成,权值就是多个子集权值的最小值。

    对于某个集合的所有结点的剩余水量都相等,考虑如何流通传递水量。

    • 结论 1 :流通传递的水量经过的边构成的一定是一颗树。

      证明:首先必然是一整个连通块,若不是,则会被第二种情况考虑,其次,若边构成了一个环,则肯定能隐去一条最大边,剩余的的传递关系仍然成立。

    • 结论 2 :每条树边最多被经过一次
      证明:类似于网络流的反向边,如果一条边正反经过多次,是可以抵消一部分的,而多次同方向经过可以叠加。

    (dp[i]) 表示集合状态为 (i) 最大的值。

    计算两种情况的贡献,通过结论1、2,可知第一种值为 (frac{suma_i-MST}{V}) ( (V) 是点数,(MST) 是集合最小生成树边权值和),第二种通过枚举子集得到 (dp[i]=max (dp[j],dp[ioplus 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;
    }
    
  • 相关阅读:
    一些精简的小技巧
    POJ题目分类(转)
    【慢慢学算法】:连通图
    【菜鸟做水题】: 杭电1004
    杭电ACM试题分类,一步一个脚印!(转)
    【慢慢学Android】:获得当前时间
    【慢慢学算法】:小白鼠排队
    【慢慢学Android】:12.Notification示例
    【慢慢学Android】:13.打电话代码
    “/”应用程序中的服务器错误。
  • 原文地址:https://www.cnblogs.com/RedreamMer/p/14471240.html
Copyright © 2011-2022 走看看