Link
Difficulty
算法难度5,思维难度6,代码难度5
Description
给定一张n个点r条边的有向图,边有长度,其中编号1~b是部门,b+1号是总部。
现在要求你将所有部门分成s组,每组的所有部门之间都要互相传递信息。
从x传递信息到y,要先从x传到总部,再由总部传到y。
要求你划分分组使得传递信息经过的总长度最小。
Solution
首先我们考虑转化一下问题。
我们发现,假如一个部门所属的分组有size个部门,那么它会向总部传size-1次信息,也会从总部接收size-1次信息。
那么我们可以求出来每个部门到总部以及总部到每个部门的最短路,记它们的和为 。
这样我们可以很轻松地设计一个状态: 代表前 个部门分了 组的最小总长度。
转移方程也很明显:
其中 。
然后这样就可以 直接dp了,但是并不能通过本题。
我们发现最优策略中,一定是 较大的分在部门数较少的分组,这样才能最小化答案,证明略。
所以我们考虑将这些点按照 从大到小排序,那么每组的部门数一定是递增的(可能相等)。
如果采用枚举前面从谁转移过来的方法,这个性质没什么用。
但是我们考虑如果用刷表法,就可以很大程度上缩小枚举范围了。
(以下默认我们在状态 下,向后转移)
首先,我们记录每个点最优情况下的最小部门数 。
这样下一个分组就起码要有 个部门了,我们可以从 开始枚举。
再一个,我们必须分够 组,所以我们要把剩下的 个部门分成至少 组,那么最多枚举到 了。
这样子我们搞出了合法后继区间 ,极大地加快了枚举速度。
时间复杂度大概算一下是个 级别的。
我写的在bzoj排rank15,还挺快。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<queue>
#define LL long long
using namespace std;
inline int read(){
int x=0,f=1;char ch=' ';
while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0' && ch<='9')x=x*10+(ch^48),ch=getchar();
return f==1?x:-x;
}
const int N=5005,M=1e5+5;
int n,b,s,r,tot,tot2;
int head[N],to[M],Next[M],val[M];
inline void addedge(int x,int y,int l){
to[++tot]=y;
Next[tot]=head[x];
head[x]=tot;
val[tot]=l;
}
int head2[N],to2[M],Next2[M],val2[M];
inline void addedge2(int x,int y,int l){
to2[++tot2]=y;
Next2[tot2]=head2[x];
head2[x]=tot2;
val2[tot2]=l;
}
int d[N],inq[N],d2[N];
queue<int> q;
inline void spfa1(){
for(int i=1;i<=n;++i)d[i]=1e9;
inq[b+1]=1;d[b+1]=0;
q.push(b+1);
while(!q.empty()){
int x=q.front();
q.pop();
inq[x]=0;
for(int i=head[x];i;i=Next[i]){
int u=to[i];
if(d[u]>d[x]+val[i]){
d[u]=d[x]+val[i];
if(!inq[u]){
inq[u]=1;
q.push(u);
}
}
}
}
}
inline void spfa2(){
for(int i=1;i<=n;++i)d2[i]=1e9,inq[i]=0;
inq[b+1]=1;d2[b+1]=0;
q.push(b+1);
while(!q.empty()){
int x=q.front();
q.pop();
inq[x]=0;
for(int i=head2[x];i;i=Next2[i]){
int u=to2[i];
if(d2[u]>d2[x]+val2[i]){
d2[u]=d2[x]+val2[i];
if(!inq[u]){
inq[u]=1;
q.push(u);
}
}
}
}
}
int a[N];
LL dp[2][N],sum[N],size[N];
inline bool cmp(LL x,LL y){return x>y;}
int main(){
n=read();b=read();s=read();r=read();
for(int i=1;i<=r;++i){
int x=read(),y=read(),l=read();
addedge(x,y,l);
addedge2(y,x,l);
}
spfa1();spfa2();
for(int i=1;i<=b;++i)a[i]=d[i]+d2[i];
sort(a+1,a+b+1,cmp);
for(int i=1;i<=b;++i)sum[i]=sum[i-1]+a[i];
for(int i=0;i<=b;++i)dp[0][i]=1e18;
dp[0][0]=0;size[0]=1;
int cur=0;
for(int i=0;i<s;++i){
for(int j=0;j<=b;++j)dp[cur^1][j]=1e18;
for(int j=0;j<=b;++j){
if(dp[cur][j]==1e18)continue;
int l=j+size[j],r=j+(b-j)/(s-i);
for(int k=l;k<=r;++k){
if(dp[cur^1][k]>=dp[cur][j]+(k-j-1)*(sum[k]-sum[j])){
dp[cur^1][k]=dp[cur][j]+(k-j-1)*(sum[k]-sum[j]);
size[k]=k-j;
}
}
}
cur^=1;
}
printf("%lld\n",dp[cur][b]);
return 0;
}