bzoj4609: [Wf2016]Branch Assignment

Description

要完成一个由s个子项目组成的项目,给b(b>=s)个部门分配,从而把b个部门分成s个组。分组完成后,每一组的任意两个点之间都要传递信息。假设在(i,j)两个点间传送信息,要先把信息加密,然后快递员从i出发到总部,再加密,在到j点。出于安全原因,每次只能携带一条消息。现在给出了道路网络、各个部门和总部的位置,请输出快递员要走的最小总距离。

Input

第一行包含四个整数n,b,s,r。n(2<=n<=5000)代表路口数,b(1<=b<=n-1)是部门数,s(1<=s<=b)是子项目数
r(1<=r<=50000)是道路数。路口标号为1~n,部门在路口1~b,总部在路口b+1。
接下来r行每行三个整数u,v,l,描述一条从u到v长度为l(0<=l<=10000)的单向边。保证没有重边,保证图强连通。

Output

输出快递员要走的最小总距离。

Sample Input

5 4 3 8

1 5 15

5 1 15

2 5 2

5 2 3

3 5 1

5 3 1

4 5 2

5 4 0

Sample Output

4

解题思路:

首先肯定是正向反向两遍SPFA处理出每个点到总部的距离和总部到每个点的距离。

如果某个点所在的部门的大小为k,那么这个点需要送出k-1次消息并接收k-1次消息。

我们把每个点的两个距离求和并排序,显然在一个块中的是这个序列上的一个区间,我们做一下前缀和。设 f[i][j] f [ i ] [ j ] 表示前i个点分j个块,最小代价。那么

f[i][j]=min(f[k][j1]+(ik1)(sum[i]sum[k])) f [ i ] [ j ] = m i n ( f [ k ] [ j − 1 ] + ( i − k − 1 ) ∗ ( s u m [ i ] − s u m [ k ] ) )

直接dp是 O(n3) O ( n 3 ) 的,但注意随着j增大,对于每个 i i 最优的 k 也是递增的,直接指针扫过去,复杂度 O(n2) O ( n 2 )

要开滚动数组。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')c=getchar(),f=-1;
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}
const int N=5005,M=50000,inf=1e9;
int n,b,s,m,tp[N];ll f[2][N],sum[N];
struct Graph
{
    int tot,first[N],nxt[M],to[M],w[M],dis[N],exist[N];
    queue<int>q;
    void add(int x,int y,int z)
    {
        nxt[++tot]=first[x],first[x]=tot,to[tot]=y,w[tot]=z;
    }
    void spfa()
    {
        for(int i=1;i<=n;i++)dis[i]=inf;
        dis[b+1]=0,q.push(b+1),exist[b+1]=1;
        while(!q.empty())
        {
            int u=q.front();q.pop(),exist[u]=0;
            for(int e=first[u];e;e=nxt[e])
            {
                int v=to[e];
                if(dis[v]>dis[u]+w[e])
                {
                    dis[v]=dis[u]+w[e];
                    if(!exist[v])q.push(v),exist[v]=1;
                }
            }
        }
    }
}G,R;
int main()
{
    //freopen("lx.in","r",stdin);
    n=getint(),b=getint(),s=getint(),m=getint();
    while(m--)
    {
        int x=getint(),y=getint(),z=getint();
        G.add(x,y,z),R.add(y,x,z);
    }
    G.spfa(),R.spfa();
    for(int i=1;i<=b;i++)sum[i]=G.dis[i]+R.dis[i];
    sort(sum+1,sum+b+1);
    for(int i=1;i<=b;i++)sum[i]+=sum[i-1];
    memset(f[0],0x3f,sizeof(f[0])),f[0][0]=0;
    for(int j=1,cur=1;j<=s;j++,cur^=1) 
    { 
        memset(f[cur],0x3f,sizeof(f[1]));
        for(int i=1;i<=b;i++) 
            for(int lst=tp[i];lst<i;lst++)
                if( f[cur][i] >= f[cur^1][lst] + ( i - lst - 1 ) * ( sum[i] - sum[lst] ) ) f[cur][i] = f[cur^1][lst] + ( i - lst - 1 ) * ( sum[i] - sum[lst] ) , tp[i] = lst;
    }
    cout<<f[s&1][b]<<'\n';
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值