2019.08.21【NOIP提高组】模拟 B 组 dfs、最小生成树+二分、dp+计算几何、floyed+dfs、spfa

0 最小比例(ratio)

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

在这里插入图片描述

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

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

N和M(2<=N<=15,2<=M<=N)


15的数据,暗示着这题不寻常的写法——暴搜
在这里插入图片描述
递归求所有点的排列(显然15是不会爆的
这样的就把点的权值固定了
然后每次求出的排列求最小生成树,求出最小的边权和
再用求出的点权和与边权和的比值更新答案,记录最小的生成树
因为是按照顺序递归的,当比率相同时不用更新答案就可以保证生成树字典序最小

#include <cstdio>
#include <algorithm>

using namespace std;

int n,m,ss,nod,edg=10000;
int d[20],cnt,f[20];
struct cv{
	int ed,x,y;
}e[400];

bool comp(cv a,cv b){
	return a.ed<b.ed;
}

int get(int x){
	if (f[x]==x) return x;
	f[x]=get(f[x]);
	return f[x];
}

void tree(int x){
	int node=0,edge=0,b[20];
	for (int i=1;i<=n;i++){
		f[i]=i;
		if ((x&(1<<(i-1)))>0) node+=d[i];
	}
	for (int i=1,j=0;i<=cnt&&j<m;i++)
	if ((x&(1<<(e[i].x-1)))&&(x&(1<<(e[i].y-1)))&&get(e[i].x)!=get(e[i].y)){
		edge+=e[i].ed;
		f[f[e[i].x]]=f[e[i].y];
		j++;
	}
	if (edge*nod<edg*node){
		edg=edge,nod=node,ss=x;
		return;
	}
}

void dfs(int dep,int k,int s){
	if (k>=m){
		tree(s);
		return;
	}
	if (dep>=n) return;
	dfs(dep+1,k+1,s|1<<dep);
	dfs(dep+1,k,s);
}

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",&d[i]);
	for (int i=1;i<=n;i++)
		for (int j=1;j<=n;j++){
			int cd;
			scanf("%d",&cd);
			if (i!=j) e[++cnt].x=i,e[cnt].y=j,e[cnt].ed=cd;
		}
	sort(e+1,e+1+cnt,comp);
	dfs(0,0,0);
	for (int i=1;i<=n;i++)
	if ((ss&1<<(i-1))>0)
		printf("%d ",i);
}

1 软件公司(company)

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

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

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

m(1<=n<=100,1<=m<=100)。


其实仔(sui)细(bian)思(xiang)考(xiang)可以得出,题目就是要求一个所有程序员中最大的完成时间最少的方案,求这个最小的最大完成时间
在这里插入图片描述
于是就选择二分答案,dp判断答案正确性

f [ i ] [ j ] f[i][j] f[i][j]表示第i个程序员,第一个任务完成了j个,还可以完成最多个数的第二个任务
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − k ] + ( m i d − a [ i ] ∗ k ) / b [ i ] ) f[i][j]=max(f[i-1][j-k]+(mid-a[i]*k)/b[i]) f[i][j]=max(f[i1][jk]+(mida[i]k)/b[i])注意不要让f出现负数的情况即转移时要保证 k ∗ a [ i ] < = m i d k*a[i]<=mid ka[i]<=mid

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

using namespace std;

int n,m,l,r;
int a[105],b[105];
int f[105][105];

void read(){
	scanf("%d%d",&n,&m);
	int xa=0,xb=0;
	for (int i=1;i<=n;i++){
		scanf("%d%d",&a[i],&b[i]);
		xa=(xa<a[i]?a[i]:xa),
		xb=(xb<b[i]?b[i]:xb),
	} 
	l=1,r=xa*m+xb*m;
}

bool dp(int x){
	memset(f,-10,sizeof f);
	for (int i=0;i<=n;i++)
		f[i][0]=0;
	for (int i=1;i<=n;i++)
		for (int j=0;j<=m;j++)
			for (int k=0;k<=j&&k*a[i]<=x;k++)
				f[i][j]=max(f[i-1][j-k]+(x-a[i]*k)/b[i],f[i][j]);
	if (f[n][m]>=m) return 1;
	return 0;
}

void bs(){
	int t;
	while (l<=r){
		int mid=(l+r)/2;
		if (dp(mid)) t=mid,r=mid-1;else l=mid+1;
	}
	printf("%d",t);
}

int main(){
	freopen("company.in","r",stdin);
	freopen("company.out","w",stdout);
	read();
	bs();
}

2 空间航行(warp)

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

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

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

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

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

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

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

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


虽然是三维的地图,但是可以转化成有n个点的无向图

星球与星球的距离是球心距离减去半径,起点终点也算做半径为0的星球
然后随便最短路,弗洛伊德spfaDij随便跑

我一开始老错,最后发现是Floyed打炸??
在这里插入图片描述
原来Floyed原理是枚举中间点更新枚举出来的路径,也就是说中间点k应放在第一重循环

#include <cstdio>
#include <math.h>
#include <cstring>

using namespace std;

int n,m;
int x[105],y[105],z[105],r[105];
double a[105][105];

double kk(int x,int y,int z,int a,int b,int c,int r,int d){
	double cd=sqrt((x-a)*(x-a)+(y-b)*(y-b)+(z-c)*(z-c));
	cd=(double)cd-r-d;
	if (cd<0) return 0;
	return cd;
}

int main(){
	freopen("warp.in","r",stdin);
	freopen("warp.out","w",stdout);
	while (1){
		scanf("%d",&n);
		if (n==-1) break;
		for (int i=1;i<=n;i++){
			scanf("%d%d%d%d",&x[i],&y[i],&z[i],&r[i]);
		}
		scanf("%d%d%d",&x[0],&y[0],&z[0]);
		r[n+1]=0;
		scanf("%d%d%d",&x[n+1],&y[n+1],&z[n+1]);
		for (int i=0;i<=n+1;i++)
			for (int j=0;j<=n+1;j++)
				a[i][j]=a[j][i]=kk(x[i],y[i],z[i],x[j],y[j],z[j],r[i],r[j]);
		for (int i=0;i<=n+1;i++)
			for (int j=0;j<=n+1;j++)
			if (i!=j)
				for (int k=0;k<=n+1;k++)
				if (j!=k&&i!=k&&a[i][k]+a[k][j]<a[i][j])
				a[i][j]=a[j][i]=a[i][k]+a[k][j];
		int ans=floor(a[0][n+1]*10+0.5);
		printf("%d\n",ans);
	}
}

3 摧 毁 巴士站(bus)

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

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

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

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


50的数据暗示着这道题不同寻常的写法——我当时就想,要么暴搜,要么状压DP

然后它就真的暴搜了。。。
每次都跑最短路它真的没有炸。。。
在这里插入图片描述
就每次都跑最短路,然后枚举删除最短路经过的点,递归枚举,如果最短路长度>k就更新答案并退出,算是找到一个方案

然后它就过了???

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

using namespace std;

int n,m,k,ans=55;
int a[55][55],s[55];
int v[40005],bz[55],d[55];

void read(){
	scanf("%d%d%d",&n,&m,&k);
	for (int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		a[u][v]=1;
	}
}

void spfa(){
	int h=0,t=1,b[55];
	memset(b,0,sizeof b);
	memset(d,0x3f,sizeof d);
	v[1]=1,b[1]=1,d[1]=0;
	while (h<t){
		int u=v[++h];
		for (int i=1;i<=n;i++){
			if (a[u][i]>0&&bz[i]==0&&d[v[h]]+a[u][i]<d[i]){
				d[i]=d[v[h]]+a[u][i];
				s[i]=v[h];
				if (b[i]==0){
					b[i]=1;
					v[++t]=i;
				}
			}
		}
		b[v[h]]=0;
	}
}

void dfs(int x){
	if (x>ans) return;
	spfa();
	if (d[n]>k){
		ans=min(ans,x);
		return;
	}
	int q[55];
	memcpy(q,s,sizeof s);
	for (int i=q[n];i>1;i=q[i]){
		bz[i]=1;
		dfs(x+1);
		bz[i]=0;
	}
}

int main(){
	freopen("bus.in","r",stdin);
	freopen("bus.out","w",stdout);
	read();
	dfs(0);
	printf("%d",ans);
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值