题解 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
原文地址:https://www.cnblogs.com/MYCui/p/14426873.html