jz集训 8.21

140

T1 最小比例

Description

图中共有N个点的完全图,每条边都有权值,每个点也有权值。要求选出M个点和M-1条边,构成一棵树,使得:

在这里插入图片描述

即所有边的权值与所有点的权值之和的比率最小。

给定N和M,以及N个点的权值,和所有的边权,要求M个点的最小比率生成树。

Input

第一行包含两个整数N和M(2<=N<=15,2<=M<=N),表示点数和生成树的点数。

接下来一行N个整数,表示N个点的边权。

最后N行,每行N列,表示完全图中的边权。所有点权和边权都在[1,100]之间。

Output

输出最小比率生成树的M个点。当答案出现多种时,要求输出的第一个点的编号尽量小,第一个相同,则第二个点的编号尽量小,依次类推,中间用空格分开。编号从1开始。

Sample Input

输入1:

3 2

30 20 10

0 6 2

6 0 3

2 3 0

输入2:

2 2

1 1

0 2

2 0

Sample Output

输出1:

1 3

输出2:

1 2

Data Constraint

对于30%数据,N<=5。

Solution

N 很小,考虑搜索。
把选择的m个点用dfs枚举出来,再做最小生成树即可。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 20;
const double INF = 2147483648.0;
int n,m;
double ANS=INF;
int wn[N],we[N][N],sum[N];
int ans[N],finalans[N];
int cnte,head[N],fa[N];
struct edge{
	int u,v,w;
}e[N*N*2];
bool cmp(edge a,edge b){
	return a.w<b.w;
}
void add(int u,int v,int w){
	e[++cnte]=(edge){u, v, w};
	head[u]=cnte;
}
int find(int x){
	return x==fa[x] ? x : fa[x]=find(fa[x]);
}
int kruscal(){
	int res=0,cnt=0;
	cnte=0;
	for(int i=1; i<=m; i++){
		for(int j=1; j<=m; j++){
			int u=ans[i],v=ans[j];
			fa[u]=u;
			add(u, v, we[u][v]);
			add(v, u, we[v][u]);
		}
	}
	sort(e+1, e+1+cnte, cmp);
	for(int i=1; i<=cnte; i++){
		int u=e[i].u,v=e[i].v,w=e[i].w;
		int fu=find(u);
		int fv=find(v);
		if(fu!=fv){
			fa[fv]=fu;
			res+=w;
			cnt++;
		}
		if(cnt==m-1){
			break;
		}
	}
	return res;
}
void dfs(int x,int y,int sum){
	if(y+n-x+1<m || y>m){
		return;
	}
	if(x==n+1){
		int tmp=kruscal();
		double ratio=(double)tmp/(double)sum;
		if(ratio<ANS){
			ANS=ratio;
			memcpy(finalans, ans, sizeof finalans);
		}
		return;
	}
	ans[y+1]=x;
	dfs(x+1, y+1, sum+wn[x]);
	dfs(x+1, y, sum);
}
int main(){
	freopen("ratio.in","r",stdin);
	freopen("ratio.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++){
		scanf("%d",&wn[i]);
	}
	for(int i=1; i<=n; i++){
		for(int j=1; j<=n; j++){
			scanf("%d",&we[i][j]);
			sum[i]+=we[i][j];
		}
	}
	dfs(1, 0, 0);
	for(int i=1; i<=m; i++){
		printf("%d ",finalans[i]);
	}
	return 0;
}


T2 软件公司

Description

一家软件开发公司有两个项目,并且这两个项目都由相同数量的m个子项目组成,对于同一个项目,每个子项目都是相互独立且工作量相当的,并且一个项目必须在m个子项目全部完成后才算整个项目完成。

这家公司有n名程序员分配给这两个项目,每个子项目必须由一名程序员一次完成,多名程序员可以同时做同一个项目中的不同子项目。

求最小的时间T使得公司能在T时间内完成两个项目。

Input

第一行两个正整数n,m(1<=n<=100,1<=m<=100)。

接下来n行,每行包含两个整数,x和y。分别表示每个程序员完成第一个项目的子程序的时间,和完成第二个项目子程序的时间。每个子程序耗时也不超过100。

Output

输出最小的时间T。

Sample Input

3 20

1 1

2 4

1 6

Sample Output

18

【样例解释】

第一个人做18个2项目,耗时18;第二个人做2个1项目,2个2项目耗时12;第三个人做18个1项目,耗时18。

Data Constraint

对于30%的数据,n<=30.

对于60%的数据,n<=60.

Solution

首先考虑朴素dp。

定义f[i][j][k]表示前i个人,1项目完成了j个,2项目完成了k个所需的最小时间。
容易得到:f[i][j][k]=min(f[i][j][k],max(f[i][js][kt],a[i]×s+b[i]×t)f[i][j][k]=min(f[i][j][k], max(f[i][j-s][k-t], a[i]\times s+b[i]\times t)
取max是因为你可以和别人一起做工作。
复杂度O(n×m4)O(n\times m^4),期望得分30pts,实际得分60pts。

考虑二分答案。
f[i][j][k]=1/0表示能否做到。
我们不需要枚举s和t了,因为枚举了一个,另外一个就可以算出来。
复杂度O(log(10000)×n×m3)O(log(10000)\times n\times m^3)
期望得分60pts

继续优化。
f[i][j]表示前i个人,1项目做了j个,2项目能做的最多个数。

f[i][j]=max(f[i][j],f[i1][jk]+midka[i]b[i])f[i][j]=max(f[i][j], f[i-1][j-k]+\lfloor \frac{mid-k*a[i]}{b[i]}\rfloor)

复杂度O(log(10000)×n×m2)O(log(10000) \times n \times m^2)
期望得分100pts。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 110;
const int INF = 0x3f3f3f3f; 
int n,m;
int a[N],b[N];
int f[N][N];
bool check(int x){
	for(int i=0; i<=n; i++){
		for(int j=0; j<=m; j++){
			f[i][j]=-INF;
		}
	}
	f[0][0]=0;
	for(int i=1; i<=n; i++){
		for(int j=0; j<=m; j++){
			for(int k=0; k<=j; k++){
				if(k*a[i]>x){
					break;
				}
				if((x-k*a[i])/b[i]>=0){
					f[i][j]=max(f[i][j], f[i-1][j-k]+(x-k*a[i])/b[i]);
				}
			}
		}
	}
	if(f[n][m]>=m){
		return 1;
	}
	return 0;
}
int main(){
	freopen("company.in","r",stdin);
	freopen("company.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++){
		scanf("%d%d",&a[i],&b[i]);
	}
	int l=1,r=100;
	while(l<r){
		int mid=(l+r)>>1;
		if(check(mid)){
			r=mid;
		}
		else{
			l=mid+1;
		}
	}
	printf("%d\n",l);
	return 0;
}


T3 空间航行

Description

你是一艘战列巡洋舰的引擎操作人员,这艘船的船员在空间中侦测到了一些无法辨识的异常信号。你的指挥官给你下达了命令,让你制定航线,驾驶战列巡洋舰到达那里。

船上老旧的曲速引擎的速度是0.1AU/s。然而,在太空中分布着许多殖民星域,这些星域可以被看成一个球。在星域的内部,你可以在任何地方任意次跳跃到星域内部的任意一个点,不花费任何时间。

你希望算出到达终点的最短时间。

Input

输入包含多组测试数据。

对于每一组数据,第一行包含一个正整数n,表示殖民星域的数量。

接下来n 行,第i 行包含四个整数Xi,Yi,Zi,Ri,表示第i个星域的中心座标为(Xi, Yi,Zi),星域的半径是Ri。

接下来两行,第一行包含值Xa,Ya,Za,告诉你当前座标为(Xa, Ya,Za)。

第二行包含值Xo,Yo,Zo,告诉你目的地座标为(Xo, Yo,Zo)。

输入以一行单独的-1 结尾。所有座标的单位都是天文单位(AU)。

Output

对于每一组输入数据,输出一行表示从目前的位置到达指定目的地的最短时间,取整到最近整数。输入保证取整是明确的。

Sample Input

1

20 20 20 1

0 0 0

0 0 10

1

5 0 0 4

0 0 0

10 0 0

-1

Sample Output

100

20

Data Constraint

每个输入文件至多包含10 个测试数据。

对于10% 的数据,n = 0。

对于30% 的数据,0<=n<=10。

对于100% 的数据,0<=n<=100,所有座标的绝对值<=10000 ,半径r<=10000。

你可以认为,你所在的星区的大小为无限大。

Solution

对于两个中心分别为xx,yy,半径为rxr_x,ryr_y的星域,我们认为他们的距离是max(disx,y(rx+ry),0)max(dis_{x,y}-(r_x+r_y), 0)的。

特别地,对于起点和中点,我们将它们看做两个r=0r=0的星域。

对于每个点,向其他所有点建距离为权值的边,跑一遍最短路即可。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
using namespace std;
const int N = 110;
const double INF = 1000000000.0;
int n;
double get_dis(int x1,int y1,int z1,int x2,int y2,int z2){
	return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2));
}
struct node{
	int x,y,z,r;
}a[N];
int cnte,head[N];
struct edge{
	int to,nxt;
	double w;
}e[N*N*2];
void add(int u,int v,double w){
	e[++cnte].nxt=head[u];
	e[cnte].to=v;
	e[cnte].w=w;
	head[u]=cnte;
}
double dis[N];
bool vis[N];
void spfa(){
	queue<int> q;
	q.push(0);
	memset(vis, 0, sizeof vis);
	for(int i=0; i<=n+1; i++){
		dis[i]=INF;
	}
	dis[0]=0;
	vis[0]=1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u]; i; i=e[i].nxt){
			int v=e[i].to;
			double w=e[i].w;
			if(dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				if(!vis[v]){
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
}
int main(){
	freopen("warp.in","r",stdin);
	freopen("warp.out","w",stdout);
	while(scanf("%d",&n)){
		cnte=0;
		memset(head, 0, sizeof head);
		
		memset(a, 0, sizeof a);
		memset(e, 0, sizeof e);
		
		if(n==-1){
			break;
		}
		for(int i=1; i<=n; i++){
			scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].z,&a[i].r);
		}
		scanf("%d%d%d",&a[0].x,&a[0].y,&a[0].z);
		scanf("%d%d%d",&a[n+1].x,&a[n+1].y,&a[n+1].z);
		for(int i=0; i<=n+1; i++){
			for(int j=i+1; j<=n+1; j++){
				double w=max(0.0, get_dis(a[i].x,a[i].y,a[i].z,a[j].x,a[j].y,a[j].z)-a[i].r-a[j].r);
				add(i, j, w);
				add(j, i, w);
			}
		}
		spfa();
		printf("%.0lf\n",dis[n+1]*10);
	}
	return 0;
}

T4 摧毁巴士站

Description

Gabiluso是最伟大的间谍之一。现在,他试图完成一个“不可能完成”的使命――减缓Colugu的军队到达机场的时间。Colugu有n个公共汽车站和m条道路。每条道路直接连接两个巴士站,所有的道路都是单向的。为了保持空气洁净,政府禁止所有军用车辆,因此,军队必须乘搭巴士去机场。两个巴士站之间,可能有超过一条道路。如果一个公共汽车站被破坏时,所有连接该站的道路将无法运作。Gabiluso需要做的是摧毁了一些公共汽车站,使军队无法在K分钟内到达机场。一辆公交车通过一条道路,都是一分钟。所有巴士站的编号从1到n。1号巴士站是在军营,第n号站是机场。军队始终从第一站出发。第一站和第n站不能被破坏,这里有大量的防御力量。当然也没有从第一站到第n站的道路。

请帮助Gabiluso来计算能完成使命所需摧毁的最低数目的巴士站。

Input

第一行包含三个整数n,m,k (2<n<=50,0<m<=4000,0<k<1000)。

接下来m行,每行2个整数s和f,表示从站s到站f有一条路。

Output

输出最少需要摧毁的巴士站数目。

Sample Input

5 7 3

1 3

3 4

4 5

1 2

2 5

1 4

4 5

Sample Output

2

Data Constraint

30%的数据N<=15。

Solution

我们进行如下操作:
1.跑最短路,处理出从1-n最短路径上的点集。如果dis[n]&gt;kdis[n]&gt;k,就尝试更新ansans
2.枚举点集中的点删除。
3.递归搜索。

复杂度分析:

无。

老师:这是O(?)O(?)算法。

你以为 ? 有这么大:
在这里插入图片描述

实际上只有这么大:
在这里插入图片描述
是不是觉得不太靠谱?

看看题解怎么说:

这样的搜索顺序使得枚举的点的数量得到控制。搜索的每一层会从L-2个点中选择一个进行删除,L是当前找到的最短路径的长度。如果当前找到的最短路径很长,虽然在这一层我们需要枚举很多个点进行删除,但也说明起点到终点的距离已经很长了,我们离答案已经很接近了。一般来说,在50个点的图中,答案应该不超过20。那么L<20。并且L比较大的情况只会出现在搜索的最后几层。这么想来,这个搜索的时间复杂度是可以接受的。

感觉靠谱了一点吗?

还可以使用迭代加深的技巧来优化(反向)这个算法。

非迭代加深: 48ms

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <ctime> 
using namespace std;
const int N = 60;
const int M = 4010;
const int INF = 0x3f3f3f3f;
int n,m,k,lim,ans=INF;
int cnte,head[N];
struct edge{
	int to,nxt;
}e[M];
void add(int u,int v){
	e[++cnte]=(edge){v, head[u]};
	head[u]=cnte;
}
int pre[N],dis[N];
bool vis[N],able[N];
void spfa(){
	queue<int> q;
	q.push(1);
	memset(dis, 0x3f, sizeof dis);
	memset(vis, 0, sizeof vis);
	memset(pre, 0, sizeof pre);
	vis[1]=1;
	dis[1]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u]; i; i=e[i].nxt){
			int v=e[i].to;
			if(able[v] && dis[v]>dis[u]+1){
				dis[v]=dis[u]+1;
				pre[v]=u;
				if(!vis[v]){
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
}
void dfs(int x){
	if(x>=ans){
		return;
	}
	spfa();
	if(dis[n]>k){
		ans=x;
		return;
	}
	int now=n;
	int tmp[N]={0},cnt=0;
	while(now!=1){
		now=pre[now];
		tmp[++cnt]=now;
	}
	for(int i=1; i<cnt; i++){
		able[tmp[i]]=0;
		dfs(x+1);
		able[tmp[i]]=1;
	}
	return;
}
int main(){
	freopen("bus.in","r",stdin);
	freopen("bus.out","w",stdout);
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1; i<=m; i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u, v);
	}
	memset(able, 1, sizeof able);
	spfa();
	if(dis[n]>k){
		puts("0");
		return 0;
	}
	memset(able, 1, sizeof able);
	dfs(0);
	printf("%d\n",ans);
	return 0;
}

迭代加深: 83ms

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <ctime> 
using namespace std;
const int N = 60;
const int M = 4010;
const int INF = 0x3f3f3f3f;
int n,m,k,lim,ans=INF;
int cnte,head[N];
struct edge{
	int to,nxt;
}e[M];
void add(int u,int v){
	e[++cnte]=(edge){v, head[u]};
	head[u]=cnte;
}
int pre[N],dis[N];
bool vis[N],able[N];
void spfa(){
	queue<int> q;
	q.push(1);
	memset(dis, 0x3f, sizeof dis);
	memset(vis, 0, sizeof vis);
	memset(pre, 0, sizeof pre);
	vis[1]=1;
	dis[1]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u]; i; i=e[i].nxt){
			int v=e[i].to;
			if(able[v] && dis[v]>dis[u]+1){
				dis[v]=dis[u]+1;
				pre[v]=u;
				if(!vis[v]){
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
}
void dfs(int x){
	if(x>=ans || x>=lim){
		return;
	}
	spfa();
	if(dis[n]>k){
		ans=x;
		return;
	}
	int now=n;
	int tmp[N]={0},cnt=0;
	while(now!=1){
		now=pre[now];
		tmp[++cnt]=now;
	}
	for(int i=1; i<cnt; i++){
		able[tmp[i]]=0;
		dfs(x+1);
		able[tmp[i]]=1;
	}
	return;
}
int main(){
	freopen("bus.in","r",stdin);
	freopen("bus.out","w",stdout);
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1; i<=m; i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u, v);
	}
	memset(able, 1, sizeof able);
	spfa();
	if(dis[n]>k){
		puts("0");
		return 0;
	}
	int start,end;
	for(lim=1; lim<n-1; lim++){
		if(ans<=lim){
			break;
		}
		memset(able, 1, sizeof able);
		dfs(0);
	}
	printf("%d\n",ans);
	return 0;
}

有没有大佬能告诉我这是为什么??

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章