2019.08.10【NOIP提高組】模擬 B 組 bfs+狀壓DP+單調棧優化+拓補排序、遞推

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(n2n)O(n2^n)

把n個小弟是否接了任務的狀態壓縮,枚舉狀態
狀態中有多少個1就代表接到第幾個任務
每個狀態由狀態中消掉任意一個1的狀態轉移,即
f[i]=maxf[ib[k]]a[s][k]/100f[i]=max{f[i-b[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錯了”

每天都在

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