洛谷 P6918 [ICPC2016 WF]Branch Assignment

题目链接: 洛谷P6918 CodeforcesGym101242B

题目大意

有一张 (n) 个点,(r) 条带权边的强连通有向图,对于 (i,jin [1,b])(w(i,j)=dis(i,b+1)+dis(b+1,j)) ,现在将序号 (1)(b) 的点划分为 (s) 个点集 (S_1,S_2,...,S_s),求 (sum_{k=1}^ssum_{i,jin S_k}w(i,j)) 的最小值。

(2leq nleq 5 000)(1leq sleq bleq n-1) ,边权 (in [0,10 000])

思路

对于一个集合 (S)

[sum_{i,jin S}w(i,j)=sum_{i,jin S}(dis(i,b+1)+dis(b+1,j))=(|S|-1)sum_{iin S}(dis(i,b+1)+dis(b+1,i)) ]

(a_i=dis(i,b+1)+dis(b+1,i)) ,则 (sum_{i,jin S}w(i,j)) 即为 ((|S|-1)sum_{iin S}a_i),设节点 (i) 所在的集合大小为 (h_i) ,那么有 (ans=min{sum_{i=1}^bh_ia_i}) ,根据排序不等式,(a_i) 单调递增 (h_i) 单调递减时答案最优,由此先将 (a_i) 递增排序,取 (A_i) 为前缀和,得到一个 (O(b^2s))(dp)

[dp_{c,i}=min_{0leq j<i}{dp_{c-1,j}+(i-j)(A_i-A_j)} ]

显然 (w(j,i)=(i-j)(A_i-A_j)) 满足四边形不等式(这里的 (w) 不是原题面中的那个),结合下图,(A_i)(i) 分别为横纵坐标,(aleq bleq cleq d)(w(a,d)+w(b,c)>w(a,b)+w(c,d)) :

从而最优决策点 (xi_{c,i}) 关于 (i) 单增,可将 (dp) 优化到 (O(bslogb)) ,经过观察,还会注意到 (xi_{c,i}) 同样是对 (c) 单增的,

证明:

(x=xi_{c,i})(y=xi_{c+1,i}) ,根据最优决策性应有:

[dp_{c-1,x}+w(x,i)leq dp_{c-1,y}+w(y,i)\ dp_{c,y}+w(y,i)leq dp_{c,x}+w(x,i) ]

移项可得:

[dp_{c-1,x}-dp_{c-1,y}leq w(y,i)-w(y,x)leq dp_{c,x}-dp_{c,y}\ dp_{c-1,x}+dp_{c,y}leq dp_{c,x}+dp_{c-1,y} ]

由于 (dp_{c,i}) 满足四边形不等式,所以 (xleq y) ,从而 (xi_{c,i}) 关于 (c) 单增。

所以 (xi_{c-1,i}leq xi_{c,i}leq xi_{c,i+1}),内层逆序枚举转移即可,时间复杂度:

[sum_{c=1}^ssum_{i=0}^b(xi_{c,i+1}-xi_{c-1,i})=sum_{i=0}^bxi_{s,i}+scdot b=O(b(b+s)) ]

(中间一步可以画个矩阵自行理解)

这个做法已经可以轻松通过本题了,但是可以发现有一个性质没有用到,即 (i-xi_{c,i}) 应随 (i) 增加而减小(后面划分的块要越来越小),利用这个性质,(dp) 可以进一步优化到 (O(nlog^2n)) ,具体做法在这里

Code:

(O(b(b+s))) 代码

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
#include<fstream>
#define mem(a,b) memset(a,b,sizeof(a))
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,b,a) for(int i=(b);i>=(a);i--)
#define N 5050
#define M 50500
#define ll long long
#define PLL pair<ll,ll>
#define mk make_pair
#define fr first
#define sc second
using namespace std;
int head[N],to[M],nxt[M],len[M];
int _head[N],_to[M],_nxt[M];
ll cnt,dis[N],_dis[N];
bool vis[N];
priority_queue<PLL,vector<PLL>,greater<PLL> > q;
int n,b,s,r;
ll a[N],A[N],best[N][N],dp[N][N];
void init(){mem(head,-1),mem(_head,-1),cnt=-1;}
void add_e(int a,int b,int *H,int *NX,int *T){
    NX[++cnt]=H[a],H[a]=cnt,T[cnt]=b;
}
bool Min(ll &a,ll b){return a>b?a=b,1:0;}
void dijkstra(ll *d,int *H,int *NX,int *T){
    mem(vis,false);
    memset(d,0x3f,(n+1)*sizeof(ll));
    d[b+1]=0;
    q.push(mk(0,b+1));
    while(!q.empty()){
        int cur=q.top().sc; q.pop();
        if(vis[cur])continue;
        vis[cur]=true;
        for(int i=H[cur];~i;i=NX[i]){
            if(Min(d[T[i]],d[cur]+len[i])) 
                q.push(mk(d[T[i]],T[i]));
        }
    }
}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("mine.out","w",stdout);
    ios::sync_with_stdio(false);
    cin>>n>>b>>s>>r;
    init();
    int u,v,l;
    rep(i,1,r){
        cin>>u>>v>>l;
        add_e(u,v,head,nxt,to);
        cnt--,add_e(v,u,_head,_nxt,_to);
        len[cnt]=l;
    }
    dijkstra(dis,head,nxt,to);
    dijkstra(_dis,_head,_nxt,_to);
    rep(i,1,b)a[i]=dis[i]+_dis[i];
    sort(a+1,a+b+1);
    rep(i,1,b)A[i]=A[i-1]+a[i];
    mem(dp,0x3f);
    dp[0][0]=0;
    rep(c,1,s){
        best[c][b+1]=b;
        per(i,b,0)rep(k,best[c-1][i],min(best[c][i+1],(ll)i))
            if(Min(dp[c][i],dp[c-1][k]+(ll)(i-k)*(A[i]-A[k])))best[c][i]=k;
    }
    cout<<dp[s][b]-A[b]<<endl;
    return 0;
}

坑1:数组传指针 ll *d 对其 memeset 的时候不能直接 sizeof(d);,这样的返回值是错的。
坑2:不能对指针指向的数据进行强制类型转换,会CE
(O(nlog^2n)) 的做法咕了,有空再补吧。

update on 2021.4.28

补上来了,洛谷最优解,做法是决策单调性+ (wqs) 二分,有个注意点,二分的上界比较玄学,设为 (2nr^2 imes10000) 在第四个点挂了,设成 (Inf) 才能过。(这一段时间改进了一下码风)

//This is a solution of O(nlog^2n)

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
#include<fstream>
#define mem(a,b) memset(a, b, sizeof(a))
#define rep(i,a,b) for (int i = (a); i <= (b); i++)
#define per(i,b,a) for (int i = (b); i >= (a); i--)
#define N 5050
#define M 50500
#define ll long long
#define PLL pair<ll, ll>
#define mk make_pair
#define fr first
#define sc second
#define Inf 0x3f3f3f3f3f3f3f3f
using namespace std;

int head[N], to[M], nxt[M], len[M];
int _head[N], _to[M], _nxt[M];
ll cnt, dis[N], _dis[N];
bool vis[N];
priority_queue<PLL, vector<PLL>, greater<PLL> > q;

int n, b, s, r;
ll a[N], A[N], best[N][N];
PLL dp[N];
struct choice{
    int l, r, i;
}c[N];

void init(){ mem(head, -1), mem(_head, -1), cnt = -1;}
void add_e(int a, int b, int *H, int *NX, int *T){
    NX[++cnt] = H[a], H[a] = cnt, T[cnt] = b;
}

bool Min(ll &a, ll b){ return a > b ? a = b, 1 : 0; }
void dijkstra(ll *d, int *H, int *NX, int *T){
    mem(vis, false);
    memset(d, 0x3f, (n+1)*sizeof(ll));
    d[b+1] = 0;
    q.push(mk(0, b+1));
    while (!q.empty()){
        int cur = q.top().sc;
        q.pop();
        if (vis[cur]) continue;
        vis[cur] = true;
        for(int i = H[cur]; ~i; i = NX[i]){
            if (Min(d[T[i]], d[cur] + len[i]))
                q.push(mk(d[T[i]], T[i]));
        }
    }
}

bool better(int i, int j, int k){
    return dp[i].fr+(k-i)*(A[k]-A[i]) < dp[j].fr+(k-j)*(A[k]-A[j]);
}
int binary_search(int l, int r, int i, int j){
    int mid;
    while(l < r){
        mid = (l+r)>>1;
        if(better(i, j, mid)) r = mid;
        else l = mid+1;
    }
    return l;
}
int cal(ll Cost){
    int l = 0, r = 0;
    dp[0] = {0, 0};
    c[0] = {1, b, 0};
    rep(i,1,b){
        while(l <= r && c[l].r < i) l++;
        c[l].l = i;
        dp[i].fr = dp[c[l].i].fr + (i-c[l].i)*(A[i]-A[c[l].i]) + Cost;
        dp[i].sc = dp[c[l].i].sc+1;
        int pos = b+1;
        while(l <= r && better(i, c[r].i, c[r].l)) pos = c[r--].l;
        if(l <= r && better(i, c[r].i, c[r].r)) pos = binary_search(c[r].l, c[r].r, i, c[r].i);
        if(l <= r) c[r].r = pos-1;
        if(pos <= b) c[++r] = {pos, b, i};
    }
    return dp[b].sc;
}
int main(){
    ios::sync_with_stdio(false);
    cin>>n>>b>>s>>r;
    init();
    int u, v, l;
    rep(i,1,r){
        cin>>u>>v>>l;
        add_e(u, v, head, nxt, to);
        cnt--, add_e(v, u, _head, _nxt, _to);
        len[cnt] = l;
    }

    dijkstra(dis, head, nxt, to);
    dijkstra(_dis, _head, _nxt, _to);
    rep(i,1,b) a[i] = dis[i] + _dis[i];
    sort(a+1, a+b+1);
    rep(i,1,b) A[i] = A[i-1] + a[i];
    
    ll left = 0, right = Inf, mid;
    while(left < right){
        mid = (left+right)>>1;
        if(cal(mid) <= s) right = mid;
        else left = mid+1;
    }
    cal(left);
    cout<< dp[b].fr-left*s-A[b] <<endl;
    return 0;
}
原文地址:https://www.cnblogs.com/Neal-lee/p/14491336.html