综合题 - 图论与动规

最短路 + 动规

[ZJOI2006]物流运输

题目

题目传送门:luogu1772
题目描述
物流公司要把一批货物从码头A运到码头B。由于货物量比较大,需要n天才能运完。货物运输过程中一般要转停好几个码头。物流公司通常会设计一条固定的运输路线,以便对整个运输过程实施严格的管理和跟踪。由于各种因素的存在,有的时候某个码头会无法装卸货物。这时候就必须修改运输路线,让货物能够按时到达目的地。但是修改路线是—件十分麻烦的事情,会带来额外的成本。因此物流公司希望能够订一个n天的运输计划,使得总成本尽可能地小。

输入格式
第一行是四个整数n(l≤n≤100)、m(l≤m≤20)、K和e。n表示货物运输所需天数,m表示码头总数,K表示每次修改运输路线所需成本,e表示航线条数。接下来e行每行是一条航线描述,包括了三个整数,依次表示航线连接的两个码头编号以及航线长度(>0)。其中码头A编号为1,码头B编号为m。单位长度的运输费用为1。航线是双向的。再接下来一行是一个整数d,后面的d行每行是三个整数P(1<P<m),a,b(1≤a≤b≤n)。表示编号为P的码头从第a天到第b天无法装卸货物(含头尾)。同一个码头有可能在多个时间段内不可用。但任何时间都存在至少一条从码头A到码头B的运输路线。

输出格式
包括了一个整数表示最小的总成本。总成本=n天运输路线长度之和+K*改变运输路线的次数。

输入输出样例
输入 #1 复制
5 5 10 8
1 2 1
1 3 3
1 4 2
2 3 2
2 4 4
3 4 1
3 5 2
4 5 2
4
2 2 3
3 1 1
3 3 3
4 4 5
输出 #1 复制
32
说明/提示
【样例输入说明】

在这里插入图片描述

上图依次表示第1至第5天的情况,阴影表示不可用的码头。

【样例输出说明】

前三天走1-4-5,后两天走1-3-5,这样总成本为(2+2)*3+(3+2)*2+10=32。

_NOI导刊2010提高(01)

分析

可以分成两部分 最短路 + DP
因为图是变化的,复杂度允许,可以每天跑一遍最短路
d p [ i ] dp[i] dp[i]表示到第 i i i天总最小费用
则转移方程
d p [ i ] = m i n ( d p [ i ] , d p [ j − 1 ] + ( i − j + 1 ) ∗ d i s [ m ] + k ) ; dp[i]=min(dp[i],dp[j-1]+(i-j+1)*dis[m]+k); dp[i]=min(dp[i],dp[j1]+(ij+1)dis[m]+k);

代码

/********************
User:Mandy
Language:c++
Problem:luogu1772 物流运输 
********************/
#include<bits/stdc++.h>

using namespace std;

const int maxn=105;
const int maxm=25;
const int maxe=10005;

int n,m,size,k,e,d,ans;
int first[maxm],dis[maxm],dp[maxn];
int waf[maxm][maxn];
bool vis[maxe],close[maxm];

typedef pair<int,int>pir;

priority_queue<pir,vector<pir>,greater<pir> >q;

struct Edge
{
	int v,w,nt;
}edge[maxe<<1];


template<typename T>inline void read(T &x)
{
	x=0;bool f=0;char ch=getchar();
	while(ch<'0'||ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	if(f)x=-x;
}

template<typename T>void putch(const T x)
{
	if(x>9) putch(x/10);
	putchar(x%10|48);
} 

template<typename T>inline void put(const T x)
{
	if(x<0) putchar('-'),putch(-x);
	else putch(x);
}

void docu()
{
	freopen("trans1.in","r",stdin);
}

void eadd(int u,int v,int w)
{
	edge[++size].v=v;
	edge[size].w=w;
	edge[size].nt=first[u];
	first[u]=size;
}

void readdata()
{
	read(n);read(m);read(k);read(e);
	for(int i=1;i<=e;++i)
	{
		int u,v,w;
		read(u);read(v);read(w);
		eadd(u,v,w);
		eadd(v,u,w);
	}
	read(d);
	for(int i=1;i<=d;++i)
	{
		int p,a,b;
		read(p);read(a);read(b);
		for(int j=a;j<=b;++j) waf[p][j]=1;
	}
}

void dijkstra()
{
	q.push(make_pair(0,1));
	memset(dis,0x3f3f3f3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[1]=0;
	
	while(!q.empty())
	{
		int u=q.top().second;
		q.pop();
		
		if(vis[u]) continue;
		vis[u]=1;
		
		for(int i=first[u];i;i=edge[i].nt)
		{
			int v=edge[i].v,w=edge[i].w;
			if(close[v]) continue;
			
			if(dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				q.push(make_pair(dis[v],v));
			}
		}
	}
}

void work()
{
	memset(dp,0x3f3f3f3f,sizeof(dp));
	dp[0]=0;
	
	for(int i=1;i<=n;++i)
	{
		memset(close,0,sizeof(close));
		for(int j=i;j>=1;--j)
		{
			for(int l=1;l<=m;++l) if(waf[l][j]) close[l]=1;//j~i中所有至少有一天不能开的港口 
			dijkstra();//求j~i的共同最短路径 
			if(dis[m]==0x3f3f3f3f) break;//如果无法到达,则后面也无法到达 
			dp[i]=min(dp[i],dp[j-1]+(i-j+1)*dis[m]+k);
		}
	}
	put(dp[n]-k);
}

int main()
{
//	docu();
	readdata();
	work();
	return 0;
}

最小生成树 + 动规

新三国争霸

传送门:codevs1403

题目

描述 Description
PP 特别喜欢玩即时战略类游戏,但他觉得那些游戏都有美中不足的地方。灾害总不降临道路,而只降临城市,而且道路不能被占领,没有保护粮草的真实性。于是他就研发了《新三国争霸》。
在这款游戏中,加入灾害对道路的影响(也就是一旦道路W[i,j]受到了灾害的影响,那么在一定时间内,这条路将不能通过)和道路的占领权(对于一条道路W[i,j],至少需要K[i,j]个士兵才能守住)。
PP可真是高手,不一会,就攻下了N-1座城市,加上原来的就有N座城市了,但他忽略了一点……那就是防守同样重要,不过现在还来的及。因为才打完仗所以很多城市都需要建设,PP估算了一下,大概需要T天。他现在无暇分身进攻了,只好在这T天内好好的搞建设了。所以他要派士兵占领一些道路,以确保任何两个城市之间都有路(不然敌人就要分而攻之了,是很危险的)。士兵可不是白干活的,每个士兵每天都要吃掉V的军粮。因为有灾害,所以方案可能有变化(每改变一次就需要K的军粮,初始方案也需要K的军粮)。
因为游戏是PP编的,所以他知道什么时候有灾害。PP可是一个很节约的人,他希望这T天在道路的防守上花最少的军粮。
输入格式 InputFormat
第一行有5个整数N,M,T,V,K。N表示有城市数,M表示道路数,T表示需要修养的天数,V表示每个士兵每天吃掉的军粮数,K表示修改一次花掉的军粮数。
以下M行,每行3个数A,B,C。表示A与B有一条路(路是双向的)需要C个士兵才能守住。
第M+2行是一个数P,表示有P个灾害。
以下P行,每行4个数,X,Y,T1,T2。表示X到Y的这条路,在T1到T2这几天都会受灾害。
输出格式 OutputFormat
T天在道路的防守上花费最少的军粮。
样例输入 SampleInput
3 3 5 10 30
1 2 1
2 3 2
1 3 4
1
1 3 2 5
样例输出 SampleOutput
180
数据范围和注释 Hint
对于所有数据:N<=300,M<=5000 ,T<=50,P<=8000

分析

最小生成树 + DP;
d p [ i ] dp[i] dp[i]表示到第 i i i天总共的最少费用
方程:(ans是第j~i天的共有的最小生成树最低费用)
d p [ i ] = m i n ( d p [ i ] , d p [ j − 1 ] + ( i − j + 1 ) ∗ a n s + K dp[i] = min(dp[i],dp[j - 1] + (i - j + 1) * ans + K dp[i]=min(dp[i],dp[j1]+(ij+1)ans+K

代码

/*************************
User:Mandy.H.Y
Language:c++
Problem:three
Algorithm: 
*************************/

#include<bits/stdc++.h>
#define Max(x,y) ((x) > (y) ? (x) : (y))
#define Min(x,y) ((x) < (y) ? (x) : (y))

using namespace std;

const int maxn = 305;
const int maxm = 5005;

int T,N,M,size,V,K,P,now,pre,ans;
int father[maxn]; 
int dp[55];
bool vis[maxn][maxn];
//因为是按边禁,就标记边而不是点 qwq 

struct Val{
	int u,v,w;
}val[maxm];

struct Dis{
	int x,y,l,r;
}dis[8005];

template<class T>inline void read(T &x){
	x = 0;bool flag = 0;char ch = getchar();
	while(!isdigit(ch)) flag |= ch == '-',ch = getchar();
	while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48),ch = getchar();
	if(flag) x = -x;
}

template<class T>void putch(const T x){
	if(x > 9) putch(x / 10);
	putchar(x % 10 | 48);
} 

template<class T>void put(const T x){
	if(x < 0) putchar('-'),putch(-x);
	else putch(x);
}

void file(){
	freopen("three.in","r",stdin);
//	freopen("three.out","w",stdout);
}

void add(int u,int v,int w){
	val[++size].v = v;
	val[size].u = u;
	val[size].w = w;
}

bool cmp(const Val &a,const Val &b){
	return a.w < b.w;
}

bool cmp1(const Dis &a,const Dis &b){
	if(a.l != b.l) return a.l < b.l;
	else return a.r < b.r;
}

void readdata(){
	read(N);read(M);read(T);read(V);read(K);
	for(int i = 1;i <= M; ++ i){
		int u,v,w;
		read(u);read(v);read(w);
		add(u,v,w);
	}
	
	sort(val + 1,val + 1 + M,cmp);
	
	read(P);
	
	for(int i = 1;i <= P; ++ i){
		read(dis[i].x);	read(dis[i].y);	read(dis[i].l);	read(dis[i].r);
	}

	sort(dis + 1,dis + 1 + P,cmp1);
}

int find(int x){
	return father[x] == x ? x : father[x] = find(father[x]);
}

void merge(int x,int y){
	father[find(x)] = find(y);
}

int kruskal(int x,int y){
	for(int i = 1;i <= N; ++ i) father[i] = i;
	memset(vis,0,sizeof(vis));

	for(int i = 1;i <= P; ++ i){
		if(dis[i].l > y) break;
		if(dis[i].l <= y && x <= dis[i].r){
			vis[dis[i].x][dis[i].y] = 1;
			vis[dis[i].y][dis[i].x] = 1;
		}
	}
	
	int cnt = 0,cost = 0;
	
	for(int i = 1;i <= M; ++ i){
		int v = val[i].v;
		int u = val[i].u;
		int w = val[i].w;
		
		if(vis[u][v]) continue;
		if(find(u) != find(v)){
			merge(u,v);
			cost += w * V;
			++cnt;
		}
		if(cnt == N - 1) return cost;
	}
	return -1;
}

void work(){
	memset(dp,0x3f3f3f3f,sizeof(dp));
	dp[0] = 0;
	for(int i = 1;i <= T; ++ i){
		
		for(int j = i;j >= 1; -- j){
			int ans = kruskal(j,i);
			if(ans == -1) continue;
			dp[i] = min(dp[i],dp[j - 1] + (i - j + 1) * ans + K);
		}
		
	}
	put(dp[T]);
}

int main(){
//	file();
	readdata();
	work();
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值