任务分配「最短路+DP」

任务分配「最短路+DP」

题目描述

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

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

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

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

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

输入格式

每个测试点第一行为四个正整数 \(n,b,s,m\) ,含义如题目所述。

接下来 \(m\) 行,每行三个非负整数 \(u,v,l\),表示从点 \(u\) 到点 \(v\) 有一条权值为 \(l\) 的有向边,数据保证图是强连通的,也就是任意两个点之间都可以互相走到。

输出格式

对每组数据输出一行一个非负整数表示答案。

样例

样例输入

5 4 2 10
5 2 1
2 5 1
3 5 5
4 5 0
1 5 1
2 3 1
3 2 5
2 4 5
2 1 1
3 4 2

样例输出

13

数据范围与提示

对于所有的测试点,均有 \(n<= 5000,m ≤ 50000, 1 ≤ s ≤ b < n, 0 ≤ l ≤ 10000\),给定的有向图合法且强连通。

思路分析

  • 求最短路部分不再赘述,这题唯一不一样的地方就是要从一个节点计算出两个不同方向的最短路,建一条反向边即可,然后将分部到总部和总部到分部这两部分加在一起视为一个分部的贡献值,命名为 \(g\)
  • 难搞的部分在于如何合理地进行分组。首先考虑每一组 长度为 \(len\)\(x\) 进行通讯的花费就是 \(\sum_{i∈x}g[i]*(len-1)\)
  • 既然已经知道了计算方式,那如何才能将答案最优化?将式子拆成两部分,\(\sum g[i]\)\((len-1)\) ,为了使答案最小,就需要使长度大的分组的 \(\sum g[i]\) 尽可能小,长度小的尽可能大,如果不这样,那你会出现一个两者都很大的,这时侯答案的增加量一定是大于答案的减小量的,所以一定不是最优解
  • 那么 \(DP\) 转移方程就可以得出了,定义 \(f[i][j]\) 为前 \(i\) 组包含 \(j\) 个分部的花费,转移方程利用前缀和处理一下就是 \(f[i][j]=min(f[i][j],f[i-1][k]+(sum[j]-sum[k])*(j-k-1))\),其中 \(i<= k < j\)
  • 但这时直接暴力转移时间效率并不允许,所以应用一下上面那条性质,我们将 \(g\) 值排序,那么越靠前的元素形成的分组就越长,越靠后的越小,所以我们的就可以缩减转移时的区间长度,从平均长度开始枚举就可以保证不会漏下最优解,这时 \(j-j/i <= k <j\)

\(Code\)

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define R register
#define N 5010
#define M 500010
#define int long long
using namespace std;
inline int  read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
int n,m,s,b,f[N][N],head[N],head2[N],dis1[N],dis2[N],dis[N],sum[N];
bool vis[N];
struct edge{
	int to,next,dis;
}e[M],e2[M];
int len,len2;
void add1(int u,int v,int w){
	e[++len].to = v;
	e[len].dis = w;
	e[len].next = head[u];
	head[u] = len;
}
void add2(int u,int v,int w){
	e2[++len2].to = v;
	e2[len2].dis = w;
	e2[len2].next = head2[u];
	head2[u] = len2;
}
struct node{
	int dis,num;
	node(){}
	node(int _dis,int _num){dis = _dis,num = _num;}
	bool operator <(const node &a)const{
		return dis > a.dis;
	}
};
void Dij(int x){
	memset(dis1,0x3f,sizeof(dis1));
	priority_queue<node>q;
	dis1[x] = 0;
	q.push(node(0,x));
	while(!q.empty()){
		node p = q.top();q.pop();
		int u = p.num;
		if(vis[u])continue;
		vis[u] = 1;
		for(int i = head[u];i;i = e[i].next){
			int v = e[i].to;
			if(dis1[v]>dis1[u]+e[i].dis){
				dis1[v] = dis1[u]+e[i].dis;
				q.push(node(dis1[v],v));
			}
		}
	}
	memset(dis2,0x3f,sizeof(dis2));
	memset(vis,0,sizeof(vis));
	priority_queue<node>q2;
	dis2[x] = 0;
	q2.push(node(0,x));
	while(!q2.empty()){
		node p = q2.top();q2.pop();
		int u = p.num;
		if(vis[u])continue;
		vis[u] = 1;
		for(int i = head2[u];i;i = e2[i].next){
			int v = e2[i].to;
			if(dis2[v]>dis2[u]+e2[i].dis){
				dis2[v] = dis2[u]+e2[i].dis;
				q2.push(node(dis2[v],v));
			}
		}
	}
	for(int i = 1;i <= b;i++)dis[i] = dis1[i]+dis2[i];// 对应上面的 g
}
signed main(){
	freopen("B.in","r",stdin);
	freopen("B.out","w",stdout);
	n = read(),b = read(),s = read(),m = read();
	memset(f,0x3f,sizeof(f));
	for(R int i = 1;i <= m;i++){
		int u,v,l;u = read(),v = read(),l = read();
		add1(u,v,l),add2(v,u,l);
	}
	Dij(b+1);
	sort(dis+1,dis+b+1);
	for(int i = 1;i <= b;i++)sum[i] = sum[i-1]+dis[i];	
	f[0][0] = 0;
	for(int i = 1;i <= s;i++){
		for(int j = 1;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;
}
posted @ 2020-08-20 06:46  HH_Halo  阅读(234)  评论(0编辑  收藏  举报