10.22

3.任务分配

 (assignment.pas/c/cpp)

【问题描述】

    一家公司最近在做一个机密项目,该机密项目由s个子项目构成。

    这家公司有b个分部,它们所分布的地方可以看作一个n个点(从1n编号)m条边的带权有向图,其中分部分别位于编号为1,2,3...b的点,而总部位于编号为b+1的点,现在总部要将这s个子项目分配给不同的分部来做,每个子项目可能由一个或多个分部共同完成,但每个分部都有且仅有一个子项目。

    每到月底,参与同一个子项目的所有分部之间都要进行通信,也就是每个分部都要向与它参与同一个子项目的所有其它分部各自发送消息。

    而为了保密性,总部规定,若分部i需要给分部j发送消息,则需要分部i派出一个专门负责将消息从分部i送到分部j的人(这个人只能负责这一项任务)携带用分部i的密钥加密的消息前往总部,接着总部用分部i的密钥解密消息,并用分部j的密钥加密,之后这个人将重新加密后的消息送到分部j,而这个信息传递过程的代价便是这个人从i到达总部再走到j所经过的道路的长度之和。

    现在总部想知道,如何将这s个子项目分配给b个分部,才能使得月底所需要的通信总代价最低,为了方便,你只需要输出这个最低的总代价即可。

 

【数据规模与约定】

    每个测试点5分,各个测试点数据范围如下:

测试点编号

1-6

7-13

14-20

    除此之外,数据中可能会均匀出现一些s值比较小的点。

    对于所有的测试点,均有,给定的有向图合法且强连通。

 

【问题分析】

    题目来源:World Final 2016 B: Branch Assignment

    首先,我们可以很方便地求出总部到每个分部的最短路,再通过将边反向求出每个分部到总部的最短路。

    dis[i]为分部i到总部和总部到分部i的两条最短路长度之和。

    则接下来的任务就是将dis[1],dis[2],...dis[b]分成s组,每组的代价为该组内dis之和*(该组内dis元素个数-1),目标是最小化所有组的代价之和。

    dis从小到大排序,显然一定存在一个最优划分方案,其中每组的所有元素都是排完序后连续的一段(可以使用调整法证明)。

    则我们有动态规划做法如下:f[i][j]表示前i个元素被划分成j段的最小代价,转移方程f[i][j]=min(f[k][j-1]+(dis[k+1]+...+dis[i])*(i-k-1))

    直接这样做时间复杂度是O(n³)的,但是注意到,在最优解中,dis从小到大依次划分所得到的段的长度一定是单调不增的,则上述DP转移中k的最优取值一定在[i-i/j,i)之间,于是总转移复杂度就是O(n²(1/1+1/2+1/3+...+1/n)) =O(n²logn),足以通过本题。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e4+10;
int n,b,s,m,tot1,tot2,ver1[maxn],Next1[maxn],lin1[maxn],ver2[maxn],Next2[maxn],lin2[maxn];
ll edge1[maxn],edge2[maxn],d1[maxn],d2[maxn],d[maxn],sum[maxn],f[5010][5010];
void add(int x,int y,ll z){
	ver1[++tot1]=y;Next1[tot1]=lin1[x];lin1[x]=tot1;edge1[tot1]=z;
}
void add1(int x,int y,ll z){
	ver2[++tot2]=y;Next2[tot2]=lin2[x];lin2[x]=tot2;edge2[tot2]=z;
}
void dijkstra(){
	memset(d1,0x3f,sizeof(d1));
	memset(d2,0x3f,sizeof(d2));
	priority_queue<pair<ll,int > >q;
	q.push(make_pair(0,b+1));d1[b+1]=0;
	while(q.size()){
		int x=q.top().second;q.pop();
		for(int i=lin1[x];i;i=Next1[i]){
			int y=ver1[i];
			if(d1[y]>d1[x]+edge1[i]){
				d1[y]=d1[x]+edge1[i];
				q.push(make_pair(-d1[y],y));
			}
		}
	}
	priority_queue<pair<ll,int > > p;
	p.push(make_pair(0,b+1)); d2[b+1]=0;
	while(p.size()){
		int x=p.top().second;p.pop();
		for(int i=lin2[x];i;i=Next2[i]){
			int y=ver2[i];
			if(d2[y]>d2[x]+edge2[i]){
				d2[y]=d2[x]+edge2[i];
				p.push(make_pair(-d2[y],y));
			}
		}
	}
	for(int i=1;i<=b;i++) d[i]=d1[i]+d2[i];
}
int main(){
	freopen("assignment.in","r",stdin);
	freopen("assignment.out","w",stdout);
	scanf("%d%d%d%d",&n,&b,&s,&m);
	for(int i=1;i<=m;i++){
		int x,y; ll z;
		scanf("%d%d%lld",&x,&y,&z);
		add(x,y,z);add1(y,x,z);
	}
	dijkstra();
	sort(d+1,d+b+1);
	for(int i=1;i<=b;i++) sum[i]=sum[i-1]+d[i];
	memset(f,0x3f,sizeof(f));
	f[0][0]=0;
	for(int i=1;i<=s;i++)
	for(int j=i;j<=b;j++)
	for(int k=j-j/i;k<j;k++){
		f[i][j]=min(f[i][j],f[i-1][k]+(sum[j]-sum[k])*(j-k-1));
	}
	printf("%lld\n",f[s][b]);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值