08-27~29 HDU1010 USACO4.3~4.4 搜索剪枝,模擬,拓撲,網絡流

 這兩天木有一直做題,給我的Defy刷了MIUI,玩機去了...話說MIUI還真不錯..


 言歸正傳,前兩天和隊友在HDU上掛了華中北賽區的決賽題目,完全被虐啊,根本不在狀態,希望比賽的時候千萬不要出  現這種狀況了...收起受傷的心靈,繼續努力做題,提高自己吧,畢竟起步太晚,想一步登天是不可能的,一步一步來吧..

 

 今天晚上終於把USACO第四章的題目做完了..考慮要不要停一段時間在做,因爲現在的題目已經超出了我的水平,  很難自己想出來,這樣難以達到好的做題效果,另外,也該開始回顧一下以前做過的題目和代碼了,溫故知新,很多東西都  有點忘了.


 HDU 1010Tempter of the Bone 搜索+剪枝

二維迷宮,給定起點和終點,以及牆的位置,問能否在T時間時到達終點,不能走重複的路,給出的迷宮0<m,n<8 T<=50

搜索自然會超時,經過不斷的剪枝,終於把這題優化到了0MS,一個BFS加兩個DFS,這題是一道搜索的好題

剪枝一:如果所有能走的部分小於T,必然不能走完

剪枝二:奇偶性,從一個格子到另一個格子的步數的奇偶性是固定的,如果步數和時間奇偶性不同,也必然不能到達..只需要判斷一次就可以了,如果步數和剩餘時間同奇,那麼走一步之後就是同偶了..另外有不少人說這是強力剪枝,其實不要這個剪枝也是0MS AC的...

剪枝三:最短路徑,先從終點做一遍BFS,找到所有點到終點的最短路,之後DFS時如果剩下的時間T大於這點到終點的最短路,就不需要繼續拓展這個點了,加上這個剪枝基本就能AC了,600MS+..

剪枝四:比較給力的一個剪枝,加上之後就0MS了..搜索到一個點後,對它接下來能走的塊進行搜索,記錄最大值,以及判斷終點是否在這個塊中,如果最大值小於剩餘時間T,或者終點不在這個塊中,也就不需要繼續拓展了

#include <cstdio>
#include <queue>
using namespace std;
int n,m,t;
char smaze[10][10];
int vis[10][10],maze[10][10],vis2[10][10];
int str,stc,enr,enc,canz,canz2,hzd;
queue<pair<int,int> > q;
int dr[]={1,-1,0,0},dc[]={0,0,1,-1};
void bfs(int len,int step){
	int s2=0;
	for(int i=0;i<len;i++){
		int olr=q.front().first;
		int olc=q.front().second;
		q.pop();	
		for(int i=0;i<4;i++){
			int nr=olr+dr[i],nc=olc+dc[i];
			if(nr>=1&&nr<=n&&nc>=1&&nc<=m&&!vis[nr][nc]&&smaze[nr][nc]!='X'){
				canz++;
				vis[nr][nc]=1;
				maze[nr][nc]=step;
				q.push(make_pair(nr,nc)); 
				s2++;
			}
		}
	}	
	if(s2)bfs(s2,step+1);
}
int dfs2(int pr,int pc){
	canz2++;
	if(pr==enr&&pc==enc)hzd=1; 
	for(int i=0;i<4;i++){
		int nr=pr+dr[i],nc=pc+dc[i];
		if(pr>=1&&pr<=n&&pc>=1&&pc<=m&&!vis[nr][nc]&&!vis2[nr][nc]&&t>=maze[nr][nc]){
			vis2[nr][nc]=1;
			dfs2(nr,nc);				
		}
	}
	return 0;
}
int dfs(int pr,int pc,int t){
	/*
		搜索剩下能走塊的最大值canz2,以及標記終點是否在這個塊中 
	*/ 
	memset(vis2,0,sizeof vis2);
	canz2=0,hzd=0;
	dfs2(pr,pc);
	if(canz2<t||hzd==0)return 0;
	
	if(t==0&&maze[pr][pc]==0)return 1;
	for(int i=0;i<4;i++){
		int nr=pr+dr[i],nc=pc+dc[i];
		if(pr>=1&&pr<=n&&pc>=1&&pc<=m&&!vis[nr][nc]&&t>=maze[nr][nc]){
			vis[nr][nc]=1;
			if(dfs(nr,nc,t-1))return 1;				
			vis[nr][nc]=0;
		}
	}
	return 0;
	
}
int main(){	
	while(scanf("%d%d%d",&n,&m,&t),n){
		for(int i=1;i<=n;i++){
			scanf("%s",smaze[i]+1);
			for(int j=1;j<=m;j++){
				if(smaze[i][j]=='S')str=i,stc=j;
				if(smaze[i][j]=='D')enr=i,enc=j;	
			}	
		}
		
		memset(vis,0,sizeof vis);
		for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)maze[i][j]=10000;
		maze[enr][enc]=0;
		vis[enr][enc]=1;
		q.push(make_pair(enr,enc));
		canz=0;
		/*
			找到所有點到終點的最短路,之後DFS時如果剩下的時間T大於這點到終點的最短路,
			就不需要繼續拓展這個點了,加上這個剪枝基本就能AC了,600MS+..
		*/ 
		bfs(1,1);
		/*
			1.迷宮中能走的部分<T.必然不能走完
			2.起點到終點的最短路>T
			3.時間和路徑奇偶性不同 
		*/ 
		if(canz<t||maze[str][stc]>t||((t&1)!=(maze[str][stc]&1))){
			printf("NO\n");
			continue;
		}
		memset(vis,0,sizeof vis);
		vis[str][stc]=1; 
		if(dfs(str,stc,t))printf("YES\n");
		else printf("NO\n");
	} 
	return 0;	
} 

USACO 4.3.5 Letter Game

這題比較簡單,就是給你一個字符串和26個字母對應的分數,現在有一個字典,找出字母頻數和小於目標串並且分值最大的所有串,多個串可以拼在一起

雖然沒有說明,但是仔細讀題可以發現,目標串3~7字符,字典中串也是3~7 也就是說要麼選一個,要麼選兩個...

第一遍找出最大分數,第二遍所有符合條件的串,排序輸出即可

/*
ID: swm80232
PROG:lgame 
LANG: C++
*/
#include <fstream>
#include <cstdio>
#include <string>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
using namespace std;
string col;
string dict[40001],dictl;
string res[40001]; 
int score[40001],scn,ds,rp=0;
int h[27],h2[27],slen,bscore=0;
int sc[26]={2,5,4,4,1,6,5,5,1,7,6,3,5,2,3,5,7,2,1,2,4,6,6,7,5,7};
//讀取文件 
void read(){
	memset(h,0,sizeof h);
	ds=0;
	
	cin>>col;
	slen=col.length();
	for(int i=0;i<slen;i++){
		h[col[i]-'a']++;	
	}
		
	fstream f("lgame.dict");
	while(f>>dictl){
		if(dictl==".")break;
		
		if(dictl.length()>slen)continue;
	
		memset(h2,0,sizeof h2);
		scn=0;
		int yes=1;
		for(int i=0;i<dictl.length();i++){
			h2[dictl[i]-'a']++;
			scn+=sc[dictl[i]-'a']; 
			//如果出現目標串中沒有的字母,或者頻數大於目標串中字母頻數.直接不保存 
			if(h2[dictl[i]-'a']>h[dictl[i]-'a']){yes=0;break;}	
		}
		if(!yes)continue;
		
		score[ds]=scn;
		dict[ds++]=dictl;
		//記錄單單詞最大分數 
		bscore=max(bscore,scn);
		//printf("%d\n",bscore);
	}	
	f.close();
}
//找最大分值 
void gbsc(){
	for(int i=0;i<ds;i++){
		for(int j=i;j<ds;j++){
			memset(h2,0,sizeof h2);
			int yes=1;
			for(int k=0;k<dict[i].length();k++){
				h2[dict[i][k]-'a']++;	
			}	
			for(int k=0;k<dict[j].length();k++){
				h2[dict[j][k]-'a']++;
				//排除不符合條件的 
				if(h2[dict[j][k]-'a']>h[dict[j][k]-'a'])yes=0;	
			}
			/*
			for(int k=0;k<26;k++)printf("%d ",h2[k]);
			printf("\n");
			for(int k=0;k<26;k++)printf("%d ",h[k]);
			printf("\n");
			*/ 
			//記錄多單詞最大分值 
			if(yes)bscore=max(bscore,score[i]+score[j]);
		}	
	}
}
//根據分值找單詞 
void gbrs(){
	for(int i=0;i<ds;i++){
		if(score[i]==bscore)res[rp++]=dict[i];	
	}	
	for(int i=0;i<ds;i++){
		for(int j=i;j<ds;j++){
			memset(h2,0,sizeof h2);
			int yes=1;
			for(int k=0;k<dict[i].length();k++){
				h2[dict[i][k]-'a']++;	
			}	
			for(int k=0;k<dict[j].length();k++){
				h2[dict[j][k]-'a']++;
				if(h2[dict[j][k]-'a']>h[dict[j][k]-'a'])yes=0;	
			}
			if(yes&&bscore==score[i]+score[j]){
				res[rp++]=dict[i]+" "+dict[j];	
			}
		}	
	}
}
int main(){
    freopen("lgame.in","r",stdin);
    freopen("lgame.out","w",stdout);
	read();
	gbsc();
	printf("%d\n",bscore);
	gbrs(); 
	sort(res,res+rp);
	for(int i=0;i<rp;i++){
		cout<<res[i]<<endl;	
	}
    //system("pause");
    return 0;
}

USACO 4.3.4 Street Race

一道有關聯通分量的搜索,第一問枚舉每個點搜索起點終點是否可達即可

第二問,很容易證明,符合要求的點必然在第一問的解的集合中,否則起點就可以直通終點了~~至於怎麼解決,採用一種染色的方法,將所有起點能到達的點染成綠色,中間點能到達的點染成紅色,如果地圖上沒有一個點被染成兩種顏色,則該點是符合條件的點.

/*
ID: swm80232
PROG:race3
LANG: C++
*/
#include <cstdio>
#include <string>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
using namespace std;

int n,a,map[55][105],a1[105],ap1=0,a2[105],ap2=0,vis[55],v1[55],v2[55];
int dfs(int p){
	if(p==n)return 1;
	for(int i=1;i<=map[p][0];i++){
		if(!vis[map[p][i]]){
			vis[map[p][i]]=1;
			if(dfs(map[p][i]))return 1;	
		}	
	}	
	return 0;
}
void dfsg(int p,int d){
	for(int i=1;i<=map[p][0];i++){
		if(map[p][i]!=d&&!v1[map[p][i]]){
			v1[map[p][i]]=1;
			dfsg(map[p][i],d); 
		}	
	}		
}
void dfsb(int p){
	for(int i=1;i<=map[p][0];i++){
		if(!v2[map[p][i]]){
			v2[map[p][i]]=1;
			dfsb(map[p][i]); 
		}	
	}		
}
int main(){
    freopen("race3.in","r",stdin);
    freopen("race3.out","w",stdout);	
	
	n=0;
	memset(map,0,sizeof map);
	
	while(scanf("%d",&a)){
		if(a==-2){n++;continue;}
		if(a==-1)break;
		map[n][0]++;
		map[n][map[n][0]]=a;
	}
	n--;
	
	for(int i=1;i<n;i++){
		memset(vis,0,sizeof vis);
		vis[0]=1,vis[i]=1;
		if(!dfs(0))a1[ap1++]=i;	
	}
	
	printf("%d",ap1); 
	for(int i=0;i<ap1;i++)printf(" %d",a1[i]);
	printf("\n");
	
	/*
		容易知道,中間點必然是不可避免的點 
		染色法,從起點出發能到達的點染成紅色,從另一個起點出發,能到達
		的點染成黑色,如果出現點重複被染色,說明該點不符合要求 
	*/
	for(int i=0;i<ap1;i++){
		memset(v1,0,sizeof v1);
		memset(v2,0,sizeof v2);
		v1[0]=1;
		v2[a1[i]]=1;
		dfsg(0,a1[i]);
		dfsb(a1[i]);	
		int yes=1;
		for(int i=0;i<=n;i++){
			if(v1[i]&&v2[i])yes=0;
		}
		if(yes)a2[ap2++]=a1[i];
	} 
	
	printf("%d",ap2); 
	for(int i=0;i<ap2;i++)printf(" %d",a2[i]);
	printf("\n");
	
	//system("pause");
	return 0;	
} 

USACO 4.4.1 Shuttle Puzzle 貪心+搜索

有數學方法,可以找到規律,可是我一眼看上去就直接搜索了..

BFS,自然是超時了..代碼寫的很醜陋,寫了一下午都沒A,後來還是看了大牛的代碼..這份代碼基本都是按照大牛的寫法寫的,所以想看代碼還是直接去NOCOW看吧..

一開始用位保存狀態1<<24,然後還要保存空格的位置,開了一個[24][1<<24]的數組,直接爆空間.作罷

其實這題雖然是用搜索來做,但是並不是盲目的搜索,有一種策略,就是儘量讓b像左,w向右,所以保證每一步b左移了或者w右移了,相反作用的操作不予考慮..也因爲是一種貪心的搜索,所以連判重都免了..不會有重複情況的.

/*
ID: swm80232
PROG:shuttle
LANG: C++
*/
#include <cstdio>
#include <string>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
using namespace std;
struct node{
	int ans[1000];
	int p;
	int ap;
	string s;	
}no;
int n;
string des;

void bfs(){
	//init
	queue<node> q;
	//set<string> se;
	//se.clear();
	while(!q.empty())q.pop();
	node n1;
	
	//init start and end
	n1.s.resize(2*n+1);
	des.resize(2*n+1);
	for(int i=0;i<n;i++){n1.s[i]='w';des[i]='b';}
	for(int i=n+1;i<2*n+1;i++){n1.s[i]='b',des[i]='w';}
	n1.s[n]=des[n]=' ';
	n1.p=n,n1.ap=0;
	q.push(n1); 

	while(!q.empty()){
		node now=q.front();
		node nw;
		q.pop();
		
		/*
			貪心選擇,不需要判重...因爲每次只會選擇一個方向,不會往回選擇 
		*/ 
		//if(se.count(now.s))continue;
		//se.insert(now.s);
		
		//if reach the destination
		if(now.s==des){
			for(int i=0;i<now.ap;i++){
				printf("%d",now.ans[i]+1);
				if((i+1)%20==0)printf("\n");
				else if(i!=now.ap-1)printf(" ");	
			}
			if(now.ap%20!=0)printf("\n");
		
			return;
		}		
		int p=now.p;
		memcpy(nw.ans,now.ans,sizeof now.ans);
		
		//dict order -2 -1 +1 +2
		//選擇時有一定的貪心性質 
		if(now.p>1&&now.s[p-1]=='b'&&now.s[p-2]=='w'){
			nw.s=now.s;
			nw.s[p]=nw.s[p-2],nw.s[p-2]=' ';
			nw.p=now.p-2; 
			nw.ans[now.ap]=nw.p;
			nw.ap=now.ap+1;	
			q.push(nw);			
		}
		if(now.p>0&&now.s[p-1]=='w'){	
			nw.s=now.s;
			nw.s[p]=nw.s[p-1],nw.s[p-1]=' '; 
			nw.p=now.p-1;
			nw.ans[now.ap]=nw.p;
			nw.ap=now.ap+1;
			q.push(nw);
		}
		if(now.p<2*n&&now.s[p+1]=='b'){
			nw.s=now.s;
			nw.s[p]=nw.s[p+1],nw.s[p+1]=' ';
			nw.p=now.p+1; 
			nw.ans[now.ap]=nw.p;
			nw.ap=now.ap+1;	
			q.push(nw);		
		}

		if(now.p<2*n-1&&now.s[p+1]=='w'&&now.s[p+2]=='b'){
			nw.s=now.s;
			nw.s[p]=nw.s[p+2],nw.s[p+2]=' ';
			nw.p=now.p+2; 
			nw.ans[now.ap]=nw.p;
			nw.ap=now.ap+1;	
			q.push(nw);			
		}	
		
	}
	
}
int main(){
    freopen("shuttle.in","r",stdin);
    freopen("shuttle.out","w",stdout);
	scanf("%d",&n);
	bfs();
    //system("pause");
    return 0;
}

USACO 4.4.3 拓撲排序

給N幅畫疊放之後的效果,確定疊放的順序

比較容易想到拓撲排序,先一遍掃描掃描出每個圖形的範圍(左上角和右下角確定),再一遍掃描建圖,如果B壓在了A的上面,則建一條B->A的邊

因爲要輸出所有符合要求的排序,所以把拓撲寫成DFS的形式就可以了..

/*
ID: swm80232
PROG:frameup 
LANG: C++
*/
#include <cstdio>
#include <string>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
using namespace std;
int h,w;
struct square{
	int has;
	int ar,ac,br,bc;//右上 左下 
	square(){has=0,ar=100,ac=100,br=0,bc=0;}
}sq[30];
char m[35][35];
int map[31][31];
int in[31],zms=0; 
int ans[30];
void topo(int pos,int ind[31]){

	if(pos==zms+1){
		for(int i=1;i<=zms;i++){
			printf("%c",ans[i]+'A'-1);	
		}
		printf("\n");
		return;
	}
	int p=0;
	for(p=1;p<=30;p++){
		if(ind[p]==0){
			ans[pos]=p;
			
			int pind[31];
			for(int i=1;i<=30;i++)pind[i]=ind[i];
			
			pind[p]=-1;
			for(int t=1;t<=30;t++){
				if(map[p][t])pind[t]--;	
			}
			topo(pos+1,pind);			
		}
	}	
	
}
void fg(int k,int t){
	if(k==t||map[k][t])return;
	map[k][t]=1;
	in[t]++;	
}
int main(){
    freopen("frameup.in","r",stdin);
	freopen("frameup.out","w",stdout);
	scanf("%d%d",&h,&w);
	for(int i=1;i<=h;i++){
		scanf("%s",m[i]+1);	
	}
	
	for(int i=1;i<=h;i++){
		for(int j=1;j<=w;j++){
			if(m[i][j]=='.')continue;
			int t=m[i][j]-'A'+1;
			sq[t].has=1;
			sq[t].ar=min(sq[t].ar,i);
			sq[t].ac=min(sq[t].ac,j);
			sq[t].br=max(sq[t].br,i);
			sq[t].bc=max(sq[t].bc,j);
		}	
	}	
	memset(map,0,sizeof map);
	memset(in,0,sizeof in);
	for(int k=1;k<=30;k++){
		if(sq[k].has==0){
			in[k]=-1;
			continue;
		}
		zms++;
		for(int i=sq[k].ar;i<=sq[k].br;i++){
			int t=m[i][sq[k].ac]-'A'+1;
			int t2=m[i][sq[k].bc]-'A'+1; 
			fg(k,t);
			fg(k,t2);
		}
		for(int i=sq[k].ac;i<=sq[k].bc;i++){
			int t=m[sq[k].ar][i]-'A'+1;
			int t2=m[sq[k].br][i]-'A'+1; 
			fg(k,t);
			fg(k,t2);
		}
	}

	topo(1,in);
	
    //system("pause");
    return 0;
}

USACO 4.4.2 Pollutant Control 最小割

感覺比較難的一題最小割..而且網上大多的思路和代碼都是錯的,和YJ學長討論這一題,YJ學長很快找到了USACO上兩份代碼的反例,所以只能說這題的數據實在太水了..

第一問找最小割就不用說了,EK就可以了,注意有重邊.

第二問找邊數最少的最小割,官網給的思路很巧妙,每條邊的容量乘以1001+1,再求最大流,求出的最大流爲maxflow/1001(假設不加前最大流爲maxflow,因爲邊數小於1000,所以結果不會超過MAXFLOW=maxflow*1001+1000,除去1000就是最大流),這樣做的好處是可以直接求出第二問,因爲加一條邊最大流就加1,所以邊數最少的最小割求出的最大流是最小的,這個值就是MAXFLOW%1001

第三問求出最小割的邊,這裏採用退流的方法,如果去掉這個邊之後最大流的減少量等於這條邊的容量,則該邊爲最小割中的一邊,並且去掉這條邊後,和它組成一組最小割的邊仍然構成原圖的最小割,注意如果該邊不是最小割中的邊要把邊加回去.至於字典序只要按輸入順序遍歷就可以了.

另外這題是大數,注意數據範圍

/*
ID: swm80232
PROG:milk6 
LANG: C++
*/
#include <cstdio>
#include <string>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long LL;
const LL INF = 1e10;
int n,m;
LL cap[35][35];
struct side{
	LL si,ei,ci;	
}s[1005];
LL flow[35][35];
LL EK(){
	LL a[35],f=0;
	LL p[35];
	memset(flow,0,sizeof flow);
	memset(p,0,sizeof p);
	queue<int> q;
	
	while(1){
		memset(a,0,sizeof a);
		a[1] = INF;
		q.push(1);
		while(!q.empty()){
			int u=q.front();
			q.pop();
			for(LL v=1;v<=n;v++){
				if(!a[v]&&cap[u][v]>flow[u][v]){
					p[v]=u;
					q.push(v);
					a[v]=min(a[u],cap[u][v]-flow[u][v]);	
				}	
			}	
		}
		if(a[n]==0)break;
		for(int u=n;u!=1;u=p[u]){
			flow[p[u]][u]+=a[n];
			flow[u][p[u]]-=a[n];	
		}
		f+=a[n];
	}
	return f;
}
int main(){
    freopen("milk6.in","r",stdin);
    freopen("milk6.out","w",stdout);
    memset(cap,0,sizeof cap);
    
	scanf("%lld%lld",&n,&m);
	for(LL i=0;i<m;i++)scanf("%lld%lld%lld",&s[i].si,&s[i].ei,&s[i].ci);
	for(LL i=0;i<m;i++)cap[s[i].si][s[i].ei]+=s[i].ci*1001+1;
	
	LL maxflow=EK();
	LL mflow=maxflow/1001;
	LL mside=maxflow%1001;
	
	printf("%lld %lld\n",mflow,mside);
	
	LL nside=0;
	for(LL i=0;i<m&&nside<mside;i++){
		if(flow[s[i].si][s[i].ei]!=cap[s[i].si][s[i].ei])continue;
		cap[s[i].si][s[i].ei]-=s[i].ci*1001+1;
		if(EK()==maxflow-s[i].ci*1001-1){
			printf("%lld\n",i+1);
			maxflow-=s[i].ci*1001+1;
			nside++;	
		}else cap[s[i].si][s[i].ei]+=s[i].ci*1001+1;
	}
   // system("pause");
    return 0;
}






發佈了91 篇原創文章 · 獲贊 6 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章