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;
}

有沒有大佬能告訴我這是爲什麼??

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