zoukankan      html  css  js  c++  java
  • 题解 P6918 [ICPC2016 WF]Branch Assignment

    题目分析:

    首先我们观察一下,肯定得先正着跑一遍最短路再建反图跑一遍最短路,这样可以对于所有的 (i) 求出 (dis(b + 1, i)) 以及 (dis(i, b + 1))

    然后我们继续观察,题目里面有一个条件是: 同一个子集内的点两两之间互相发送信息。

    于是我们可以知道

    性质一

    • 每一个点的贡献实际上只跟自己所处在的子集的大小有关,跟哪一些元素在一起其实并不重要。假设点 (i) 所处的集合大小为 (siz) ,那么它对于答案的贡献就会是 ((dis(i, b + 1) + dis(b + 1, i)) * (siz - 1))

    下面我们把 ((dis(i, b + 1) + dis(b + 1, i))) 叫做 (w_i)

    性质二:

    • 根据性质一,我们可以知道,对于 (w_i) 相近的一些数我们总是将它们分到一组更优。为什么呢?感性的用贪心来思考这个问题,因为如果它们的 (w_i) 比较小,我们总是希望它们能够与相对较多 的元素在同一个集合,毫无疑问的,相近的一些搭配在一起更好,如果 (w_i) 比较大也是类似的。

    通过性质二的分析,我们将 ([1,b]) 所有的点按照 (w_i) 排序。问题就转化为了将一个长度为 (b)单调不减序列 划分成 (s) 段,每一段的花费为这一段的数的和 * (这一段的长度 (-) (1))。

    这样的话我们就轻松写出一个 (n^3)(dp):

    (dp[i][j]) 表示将前 (i) 个数分成 (j) 段的最小费用。

    那么 (dp[i][j] = Min(dp[k][j - 1] + (sum[i] - sum[k]) * (i - k - 1)); (0 <= k < i))

    这样的话是肯定过不了的。

    考虑优化:

    首先上面 (dp[i][j]) 可以通过滚动数组优化为一维,这样子空间复杂度就变成了 O((n))

    在做划分第 (j) 段的时候,打表知道 随着 (i) 往右边移动,对应的最优决策点是不断右移的。

    证明:

    首先明确一点,我们进行划分的序列是 单调不减 的。下面称这个序列为 (A) 序列,这个序列的第 (i) 项我们就称之为 (A_i)

    我们假设现在在对 (dp[i]) 进行决策,(dp[i - 1]) 的最佳决策点是 (k (0 <= k < i))

    那么倘若 (dp[i]) 的最优决策点为 (j) 并且 (j < k)

    那么意味着 :

    (dp[k] + (sum[i] - sum[k]) * (i - k - 1) > dp[j] + (sum[i] - sum[j]) * (i - j - 1))

    但是又同时有:

    (dp[k] + (sum[i - 1] - sum[k]) * (i - k - 2) < dp[j] + (sum[i - 1] - sum[j]) * (i - j - 2))

    上下两式作差则有(中间的化简过程就略了,读者可以自行化简,其中重要的一步就是要将 (sum[i]) 转化为 (sum[i - 1] + A[i])):

    ((i - k - 2)A_i + sum[i] - sum[k] > (i - j - 2)A_i + sum[i] - sum[j])

    这个式子显然就矛盾了。所以可以知道,无论如何最佳决策点都是 单调不减 的。

    于是时间复杂度: O((n^2)) , 空间复杂度: O((n)) 就水过去了。

    Code

    #include <bits/stdc++.h>
    using namespace std;
    #define int long long
    inline int read() {
        int x = 0 , flag = 1;
        char ch = getchar();
        for( ; ch > '9' || ch < '0' ; ch = getchar()) if(ch == '-') flag = -1;
        for( ; ch >= '0' && ch <= '9' ; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0';
        return x * flag;
    }
    const int MAXN = 5e4 + 50;
    int n,r,b,s;
    int disA[MAXN], disB[MAXN],start[MAXN],tot = 0,book[MAXN];
    int sum[MAXN], dp[MAXN][2];
    struct Edge {
        int next, to, w;
    } edge[MAXN << 1];
    
    struct Dijk {
        int id, dis;
        bool operator <(const Dijk& A) const {return A.dis < dis;}
    } ;
    
    struct Point {
        int val,id;
    } P[MAXN];
    
    priority_queue <Dijk> q;
    
    void add(int from, int to, int w) {
        edge[++tot].next = start[from];
        edge[tot].to = to;
        edge[tot].w = w;
        start[from] = tot;
        return ;
    }
    
    void Dijkstra(int *dis,int op) {
        memset(book,0,sizeof(book));
        for(int i = 1 ; i <= n ; i ++) dis[i] = 1e9 + 7;
        dis[b + 1] = 0;
        q.push({b + 1, 0});
        while(!q.empty()) {
            int x = q.top().id, d = q.top().dis; q.pop();
            if(dis[x] != d) continue; 
            book[x] = 1;
            for(int i = start[x] ; i ; i = edge[i].next) {
                int to = edge[i].to, w = edge[i].w;
                if(i % 2 == op) continue;
                if(dis[x] + w < dis[to]) {
                    dis[to] = dis[x] + w;
                    if(!book[to]) q.push({to, dis[to]});
                }
            }
        }
        return ;
    }
    
    bool cmp(Point a, Point b) {return a.val < b.val;}
    int Q[5005];
    
    signed main() {
        n = read(), b = read(), s = read(), r = read();
        for(int i = 1 ; i <= r ; i ++) {
            int u = read(), v = read(), w = read();
            add(u, v, w); add(v, u, w);
        }
        Dijkstra(disA, 0); Dijkstra(disB, 1);
        for(int i = 1 ; i <= n ; i ++) {
            P[i].val = disA[i] + disB[i];
            P[i].id = i;
        }
        sort(P + 1, P + 1 + b , cmp);
        for(int i = 1 ; i <= b ; i ++) sum[i] = sum[i - 1] + P[i].val;
        int now = 1;
        for(int i = 1 ; i <= b ; i ++) dp[i][1] = sum[i] * (i - 1);
        for(int j = 2 ; j <= s ; j ++) {
            now ^= 1;
            memset(Q, 0, sizeof(Q));
            for(int i = 1 ; i <= b ; i ++) dp[i][now] = 1e18 + 7;
            for(int i = j ; i <= b ; i ++) {
                for(int k = i - 1 ; k >= Q[i - 1] ; k --) {
                    if(dp[k][now ^ 1] + (sum[i] - sum[k]) * (i - k - 1) < dp[i][now]) Q[i] = k;
                    dp[i][now] = min(dp[i][now], dp[k][now ^ 1] + (sum[i] - sum[k]) * (i - k - 1));
                }
            }
        }
        cout << dp[b][now];
        return 0;
    }
    
    By MYCui
  • 相关阅读:
    Ubuntu中安装XAMPP出错的解决方法
    sudo 后不用输入密码的配置
    javascript鼠标双击时触发事件大全
    PHP空值判断
    40音乐海报的创意例子
    35个令人印象深刻的创意404错误页面设计
    35个高分辨率创意苹果桌面壁纸
    38惊人的HD(高清晰度)壁纸七彩的例子
    25精心设计的联系页面例子
    pgpoolII中对 setsockopt 的利用
  • 原文地址:https://www.cnblogs.com/MYCui/p/14426873.html
Copyright © 2011-2022 走看看