【BZOJ4609】Branch Assignment(WF2016)-最短路+决策单调性优化DP

测试地址:Branch Assignment
题目大意:有一张n个点的有向强连通图,其中b个点是部门,另有一个点是总部,要把部门分成s个组,每组之内每两个部门都要相互传递信息,一个部门向另一个部门传递信息,需要从这个部门走到总部,再从总部走到另一个部门,问总的传递距离的最小值。
做法:本题需要用到最短路+决策单调性优化DP。
首先,我们显然要用两次SPFA求出所有点到总部,以及总部到所有点的距离。然后对于一个有k个部门的组,每个组要到总部k1次,又要从总部回来k1次,所以这一组的贡献就是(k1)×v(dis(v,b+1)+dis(b+1,v))(因为总部是b+1号)。
dis(v,b+1)+dis(b+1,v)为每个点的点权,我们能得到如下的结论:一定存在一种最优方案,使得选出的所有的组在点按点权排序的顺序中,是一段连续的区间。其实挺好证明,对于每一种可能的分组方案(这里的“分组方案”指每个组部门数的分配,先不考虑具体部门),我们实际上就是给每个点附上一个系数,使得系数乘上点权之和最小。根据排序不等式,在点权从小到大排列时,系数也应该从小到大排列最好。这样我们不仅得出了上面的结论,还能得到一个结论:最优方案中,后面的组总比前面的更大(因为系数是组的大小1)。
于是我们能很快写出一个O(n3)状态转移方程:
f(i,j)为前i个部门中分j个组的最优答案,有:
f(i,j)=min{f(x,j1)+(ix1)×(sum(i)sum(x))}
其中sum(i)表示点权的前缀和。
乍一看这个方程是O(n3)的,但根据一些简单的证明,对于同一个ij增大时,最优决策点也是右移的,于是我们就缩小了决策范围,又根据上面那个组会越来越大的结论,可以证明(然而我不会)时间复杂度是O(n2logn)的,可以通过此题(我也不知道怎么过的…但它就是能过)。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf=1000000000;
int n,b,s,m,first[5010]={0},tot=0;
int u[50010],v[50010];
ll c[50010],dis[5010],sum[5010];
ll f[2][5010],t[5010];
bool vis[5010]={0};
queue<int> Q;
struct edge
{
    int v,next;
    ll w;
}e[50010];

void insert(int a,int b,ll w)
{
    e[++tot].v=b;
    e[tot].next=first[a];
    e[tot].w=w;
    first[a]=tot;
}

void spfa(int S)
{
    for(int i=1;i<=n;i++)
        dis[i]=inf;
    dis[S]=0;
    Q.push(S);
    vis[S]=1;
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (dis[v]+e[i].w<dis[e[i].v])
            {
                dis[e[i].v]=dis[v]+e[i].w;
                if (!vis[e[i].v])
                {
                    Q.push(e[i].v);
                    vis[e[i].v]=1;
                }
            }
        vis[v]=0;
    }
}

int main()
{
    scanf("%d%d%d%d",&n,&b,&s,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d%lld",&u[i],&v[i],&c[i]);

    memset(first,0,sizeof(first));
    tot=0;
    for(int i=1;i<=m;i++)
        insert(u[i],v[i],c[i]);
    spfa(b+1);
    for(int i=1;i<=b;i++)
        sum[i]=dis[i];

    memset(first,0,sizeof(first));
    tot=0;
    for(int i=1;i<=m;i++)
        insert(v[i],u[i],c[i]);
    spfa(b+1);
    for(int i=1;i<=b;i++)
        sum[i]+=dis[i];

    sort(sum+1,sum+b+1);
    sum[0]=0;
    for(int i=1;i<=b;i++)
        sum[i]+=sum[i-1];

    int now=0,past=1;
    memset(f[past],0x3f,sizeof(f[past]));
    f[past][0]=0;
    for(int j=1;j<=s;j++)
    {
        memset(f[now],0x3f,sizeof(f[now]));
        for(ll i=1;i<=b;i++)
        {
            for(ll x=t[i];x<i;x++)
                if (f[now][i]>=f[past][x]+(i-x-1ll)*(sum[i]-sum[x]))
                {
                    f[now][i]=f[past][x]+(i-x-1ll)*(sum[i]-sum[x]);
                    t[i]=x;
                }
        }
        swap(now,past);
    }
    printf("%lld\n",f[past][b]);

    return 0;
}
posted @ 2018-06-28 09:29  Maxwei_wzj  阅读(151)  评论(0编辑  收藏  举报