2019.08.10【NOIP提高组】模拟 B 组 bfs+状压DP+单调栈优化+拓补排序、递推

21 篇文章 0 订阅
9 篇文章 0 订阅

0 洪水

一天, 一个画家在森林里写生,突然爆发了山洪,他需要尽快返回住所中,那里是安

全的。

森林的地图由R行C列组成,空白区域用点“.”表示,洪水的区域用“*”表示,而

岩石用“X”表示,另画家的住所用“D”表示,画家用“S”表示。

有以下几点需要说明:

1、 每一分钟画家能向四个方向移动一格(上、下、左、右)

2、 每一分钟洪水能蔓延到四个方向的相邻格子(空白区域)

3、 洪水和画家都不能通过岩石区域

4、 画家不能通过洪水区域(同时也不行,即画家不能移到某个格子,该格子在画家达到的同时被洪水蔓延到了,这也是不允许的)

5、 洪水蔓不到画家的住所。

给你森林的地图,编写程序输出最少需要花费多长时间才能从开始的位置赶回家中。
(R,C<=50)

直接搜就好
洪水蔓延,当画家在洪水肆虐前没能回家,画家就再也回不了家
画家无法追逐洪水的轨迹,洪水却能吞噬画家的足迹,命中注定有这一场不对等的博弈

#include <cstdio>
#include <cstring>

using namespace std;

const int dx[6]={1,-1,0,0};
const int dy[6]={0,0,-1,1};
int r,c,ans;
int a[55][55],w[55][55],d[55][55];
int v[4][3000],tv,th,h[4][3000];

void read(){
	scanf("%d%d",&r,&c);
	char ch[55];
	memset(a,-1,sizeof a);
	for (int i=1;i<=r;i++){
		scanf("%s",ch+1);
		for (int j=1;j<=c;j++)	
			if (ch[j]=='.')	a[i][j]=0; else
			if (ch[j]=='D') a[i][j]=1; else			
			if (ch[j]=='*') h[1][++th]=i,h[2][th]=j,h[3][th]=1; else
			if (ch[j]=='S') v[1][++tv]=i,v[2][tv]=j,v[3][tv]=1;
	}
}

void bfs(){
	int hh=0,hv=0;
	while (hh<th||hv<tv){
		int x,y,c;		
		if (hh<th){
			int c=h[3][++hh];
			while (hh<=th&&c==h[3][hh]){
				x=h[1][hh],y=h[2][hh];
				w[x][y]=h[3][hh];
				for (int i=0;i<4;i++)
				if (a[x+dx[i]][y+dy[i]]==0&&w[x+dx[i]][y+dy[i]]==0){
 					w[x+dx[i]][y+dy[i]]=h[3][hh]+1;
					h[1][++th]=x+dx[i],h[2][th]=y+dy[i],h[3][th]=h[3][hh]+1;
				}
				hh++;
			}
			hh--;
		}
		if (hv<tv){
			int c=v[3][++hv];
			while (hv<=tv&&c==v[3][hv]){
				x=v[1][hv],y=v[2][hv];
				d[x][y]=v[3][hv];
				for (int i=0;i<4;i++){
					int xx=x+dx[i],yy=y+dy[i];
					if (a[xx][yy]>=0&&w[xx][yy]==0&&d[xx][yy]==0){
						d[xx][yy]=v[3][hv]+1;
						v[1][++tv]=xx,v[2][tv]=yy,v[3][tv]=v[3][hv]+1;
						if (a[xx][yy]==1){
							ans=d[xx][yy];
							return;
						}
					}
				}
				hv++;
			}
			hv--;
		}
	}
}

int main(){
	read();	
	bfs(); 
	if (ans!=0) printf("%d",ans-1);
		   else printf("KAKTUS");
}

1 邦德I

每个人都知道詹姆斯邦德,著名的007,但很少有人知道很多任务都不是他亲自完成的,而是由他的堂弟们吉米邦德完成(他有很多堂弟),詹姆斯已经厌倦了把一个个任务分配给一个个吉米,他向你求助。

每个月,詹姆斯都会收到一些任务,根据他以前执行任务的经验,他计算出了每个吉米完成每个任务的成功率,要求每个任务必须分配给不同的人去完成,每个人只能完成一个任务。

请你编写程序找到一个分配方案使得所有任务都成功完成的概率。

输入第一行包含一个整数N,表示吉米邦德的数量以及任务的数量(正好相等,1<=N<=20)。

接下来N行,每行包含N个0到100之间整数,第i行的第j个数Aij表示吉米邦德i完成任务j成功的概率为Aij%

哎呀状压DP嘛,一眼看出来啦
但是考试时看错数据,以为2^20也就是状态数太大放不进数组,就打了暴搜50分做法
然而2^20也就100多万不多的

O ( n 2 n ) O(n2^n) O(n2n)

把n个小弟是否接了任务的状态压缩,枚举状态
状态中有多少个1就代表接到第几个任务
每个状态由状态中消掉任意一个1的状态转移,即
f [ i ] = m a x f [ i − b [ k ] ] ∗ a [ s ] [ k ] / 100 f[i]=max{f[i-b[k]]*a[s][k]/100} f[i]=maxf[ib[k]]a[s][k]/100
其中s为当前状态任务总数,k为枚举的状态中任意一个1

#include <cstdio>
#include <algorithm>

using namespace std;

int n,a[22][22];
double ans;
int b[22],s[22];
double f[2097152];

void read(){
	scanf("%d",&n);
	b[0]=1;
	for (int i=1;i<=n;i++){
		b[i]=b[i-1]*2;
		for (int j=1;j<=n;j++)
			scanf("%d",&a[i][j]);
	}
}

void dp(){
	f[0]=1;	
	for (int i=1;i<=b[n]-1;i++){		
		s[0]=0;
		for (int j=1;j<=n;j++)
			if ((i&b[j-1])>0)
				s[++s[0]]=j;
		for (int j=1;j<=s[0];j++)
			f[i]=max(f[i],f[i-b[s[j]-1]]*a[s[0]][s[j]]/100);
	}
}

int main(){
	read();
	dp();
	printf("%.6f",f[b[n]-1]*100);
}

2 餐桌

你家刚买了一套新房,想邀请朋友回来庆祝,所以需要一个很大的举行餐桌,餐桌能容纳的人数等于餐桌的周长,你想买一个能容纳最多人的餐桌,餐桌的边必须跟房间的边平行。

给你的房间的设计,计算最多能邀请的客人数。

第一行包含两个整数R和C(1<=R,C<=2000),表示房子的长和宽。

接下来R行每行S个字符(中间没有空格),“.”表示空白区域,“X”表示有障碍物,餐桌所占区域必须是空白的。

矩形的高度用up[i][j]表示,即格子(i,j)向上多少个格子到障碍物格子
一行一行枚举

这时就把每一行的矩形从左向右加入队列,当后加的矩形比前面的矩形矮时,要弹出高的矩形高出的部分,并将原矩形更新答案,然后把消掉高出部分的矩形加入后加的矩形,也就是后加的矩形宽度增加。
加入答案时,用矩形的周长=(长+宽)/ 2
emm…

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

using namespace std;

int r,c,ans;
int a[2005][2005];
int up[2005][2005];
int f[2005],w[2005],cnt;

void read(){
	scanf("%d%d",&r,&c);
	for (int i=1;i<=r;i++){
		char ch=getchar();
		int j=0;
		while (ch!='X'&&ch!='.') ch=getchar();
		while (ch=='X'||ch=='.'){
			j++;
			if (ch=='.') a[i][j]=1;
			ch=getchar();
		}
	}
	for (int i=0;i<=r;i++){
		for (int j=1;j<=c;j++)
		if (a[i][j]==0){
			for (int k=i+1;k<=r&&a[k][j]==1;k++)
				up[k][j]=up[k-1][j]+1;
		}
	}
}

void work(){
	for (int i=1;i<=r;i++){
		for (int j=1;j<=c+1;j++){
			if (a[i][j]==0){
				while (cnt){
					ans=max(ans,(f[cnt]+w[cnt])*2);
					w[cnt-1]+=w[cnt];
					cnt--;
				}
			}
			else{
				if (up[i][j]>f[cnt]) f[++cnt]=up[i][j],w[cnt]=1; else
				if (up[i][j]==f[cnt]) w[cnt]++; else{
					int cw=0;
					while (up[i][j]<f[cnt]){
						ans=max(ans,(f[cnt]+w[cnt])*2);
						cw+=w[cnt];
						cnt--;
					}
					f[++cnt]=up[i][j],w[cnt]=cw+1;
					while (up[i][j]==f[cnt-1]) w[cnt-1]+=w[cnt],cnt--; 
				}
			}
		}
	}
}

int main(){
	read();
	work();
	printf("%d",ans-1);
}

3 自行车比赛

自行车赛在一个很大的地方举行,有N个镇,用1到N编号,镇与镇之间有M条单行道相连,起点设在镇1,终点设在镇2。

问从起点到终点一共有多少种不同的路线。两条路线只要不使用完全相同的道路就被认为是不同的。
第一行两个整数:N和M(1<=N<=10000,1<=M<=100000),表示镇的数量和道路的数量。

接下来M行,每行包含两个不同的整数A和B,表示有一条从镇A到镇B的单行道。

两个镇之间有可能不止一条路连接。

用拓补排序排出顺序,按照拓补序递推,f [ i ] = f [ i ] + f [ j ] ,其中 i 由 j 点走来

这里是拓补排序简单描述
————————————————————

不难看出该算法的实现十分直观,关键在于需要维护一个入度为0的顶点的集合:
每次从该集合中取出(没有特殊的取出规则,随机取出也行,使用队列/栈也行,下同)一个顶点,将该顶点放入保存结果的List中。
紧接着循环遍历由该顶点引出的所有边,从图中移除这条边,同时获取该边的另外一个顶点,如果该顶点的入度在减去本条边之后为0,那么也将这个顶点放到入度为0的集合中。然后继续从集合中取出一个顶点…………

当集合为空之后,检查图中是否还存在任何边,如果存在的话,说明图中至少存在一条环路。不存在的话则返回结果List,此List中的顺序就是对图进行拓扑排序的结果。

描述转自 https://blog.csdn.net/dm_vincent/article/details/7714519
————————————————————

#include <cstdio>
#include <cstring>

using namespace std;

const long long mod=1000000000;
const int N=10004,M=100005;
int n,m,bz;
long long d[N];
int ls[N],y[M],ne[M],b[N];
int a[N],cnt,r[N];

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

int dfs(int x){
	b[x]=1;
	if (x==2) {b[x]=2;}
	int bz=0;
	for (int i=ls[x];i;i=ne[i]){
		if (b[y[i]]==0)
			bz|=dfs(y[i]); 
		else if (b[y[i]]==2) bz|=1; 
	}
	if (bz) b[x]=2;
	if (b[x]==2) return 1;
	return bz;
}

void victor(){
	for (int i=1;i<=n;i++)
		if (r[i]==0&&b[i]==2) a[++cnt]=i;
	for (int i=1;i<=n;i++)
	if (b[i]!=2)
		for (int j=ls[i];j;j=ne[j])
			r[y[j]]--;
	d[1]=1;
	for (int i=1;i<=cnt;i++){
		int x=a[i];
		for (int j=ls[x];j;j=ne[j])
		if (b[y[j]]==2){
			if (d[y[j]]+d[x]>=mod) 
				bz=1;
			if (y[j]==2){
				y[j]=2;
			} 
			d[y[j]]=(d[y[j]]+d[x])%mod;
			r[y[j]]--;
			if (r[y[j]]==0) a[++cnt]=y[j];
		}	
	}
}

int main(){
	read();
	dfs(1);
	victor();	
	if (bz){
		int j=0;
		for (int i=d[2];i;i/=10) j++;
		if (j<9) 
			for (int i=1;i<=9-j;i++)
				printf("0");
	}
	printf("%d",d[2]);
}

“为什么这个DFS死循环?”
“不知道,一定是你写错了”
“哦,可是我是co你的呀”
“……那一定是你co错了”

每天都在

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值