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