WF2016 BranchAssignment

版权声明:随意转载,愿意的话提一句作者就好了 https://blog.csdn.net/stone41123/article/details/84061343

Link

Difficulty

算法难度5,思维难度6,代码难度5

Description

给定一张n个点r条边的有向图,边有长度,其中编号1~b是部门,b+1号是总部。

现在要求你将所有部门分成s组,每组的所有部门之间都要互相传递信息。

从x传递信息到y,要先从x传到总部,再由总部传到y。

要求你划分分组使得传递信息经过的总长度最小。

1 s b < n 5000 , 1 r 50000 , 1 l e n 10000 1\le s\le b<n\le 5000,1\le r\le 50000,1\le len\le 10000

Solution

首先我们考虑转化一下问题。

我们发现,假如一个部门所属的分组有size个部门,那么它会向总部传size-1次信息,也会从总部接收size-1次信息。

那么我们可以求出来每个部门到总部以及总部到每个部门的最短路,记它们的和为 d ( i ) d(i)

这样我们可以很轻松地设计一个状态: d p ( i , j ) dp(i,j) 代表前 i i 个部门分了 j j 组的最小总长度。

转移方程也很明显:

d p ( i , j ) = m i n k = 1 i 1 { d p ( k , j 1 ) + ( j i 1 ) × ( s u m ( j ) s u m ( i ) ) } dp(i,j)=min_{k=1}^{i-1}\{dp(k,j-1)+(j-i-1)\times (sum(j)-sum(i))\}

其中 s u m ( i ) = j = 1 i d ( j ) sum(i)=\sum_{j=1}^i d(j)

然后这样就可以 O ( n 3 ) O(n^3) 直接dp了,但是并不能通过本题。

我们发现最优策略中,一定是 d ( i ) d(i) 较大的分在部门数较少的分组,这样才能最小化答案,证明略。

所以我们考虑将这些点按照 d ( i ) d(i) 从大到小排序,那么每组的部门数一定是递增的(可能相等)。

如果采用枚举前面从谁转移过来的方法,这个性质没什么用。

但是我们考虑如果用刷表法,就可以很大程度上缩小枚举范围了。

(以下默认我们在状态 d p ( i , j ) dp(i,j) 下,向后转移)

首先,我们记录每个点最优情况下的最小部门数 s i z e ( i ) size(i)

这样下一个分组就起码要有 s i z e ( i ) size(i) 个部门了,我们可以从 i + s i z e ( i ) i+size(i) 开始枚举。

再一个,我们必须分够 s s 组,所以我们要把剩下的 b i b-i 个部门分成至少 s j s-j 组,那么最多枚举到 i + b i s j i+\lfloor \frac{b-i}{s-j}\rfloor 了。

这样子我们搞出了合法后继区间 [ i + s i z e ( i ) , i + b i s j ] \Big[i+size(i),i+\lfloor \frac{b-i}{s-j}\rfloor\Big] ,极大地加快了枚举速度。

时间复杂度大概算一下是个 O ( n 2 l o g n ) O(n^2logn) 级别的。

我写的在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;
}

猜你喜欢

转载自blog.csdn.net/stone41123/article/details/84061343