算法竞赛入门经典(第2版)-刘汝佳-第六章解题源码(C++语言)(部分)

例题6-1(TLE/WA)

本题出现TLE的情况,因为在本题,我使用了太多的STL。在结果方面估计也会有些问题,因为对于lock/unlock的机制理解不清楚,现提出自己的错误代码。

关于双端队列的用法可以参看点击打开链接

#include<iostream>
#include<sstream>
#include<string>
#include<cstring>
#include<queue>
#include<deque>
#include<map>
using namespace std;
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	int m;
	cin>>m;
	while(m--)
	{
	queue<string> prog[15];
	queue<int> lq;
	deque<int> rq;
	map<string,int> ot;//存储每个语句的执行时间 
	int var[26];//存储变量
	memset(var,0,sizeof(var));
	int ap,qu,lockflag=0;//存储程序数量和一次执行程序的时间。 
	cin>>ap>>ot["="]>>ot["print"]>>ot["lock"]>>ot["unlock"]>>ot["end"]>>qu;
	for(int i=1;i<=ap;i++)
	{
		string s;
		while(getline(cin,s)&&s!="end")
		{
			if(!s.empty()&&s!="\n")
			{
				prog[i].push(s);
			}	
		}
		prog[i].push("end");
		rq.push_back(i);
	}
	while(!rq.empty())
	{
		int rqf,time=qu,lqin=0;
		rqf=rq.front();
		rq.pop_front();
		string stmp,rtmp[3],statement;
		stmp=prog[rqf].front();
		stringstream ss(stmp);
		ss>>rtmp[0]>>rtmp[1]>>rtmp[2];
		if(rtmp[1]=="=")
		{
			statement="=";
		}
		else
		{
			if(rtmp[0]=="print")
			statement="print";
			if(rtmp[0]=="lock")
			statement="lock";
			if(rtmp[0]=="unlock")
			statement="unlock";
			if(rtmp[0]=="end")
			statement="end";	
		}
		int endflag=0;
		while(time>=ot[statement]&&!prog[rqf].empty())
		{
			if(statement=="=")
			{
				stringstream ss(rtmp[2]);//字符串转int 
				int itmp;//
				ss>>itmp;
				var[rtmp[0][0]-'a']=itmp;				
				time-=ot["="];
				prog[rqf].pop();	
			}
			if(statement=="print")
			{
				cout<<rqf<<": "<<var[rtmp[1][0]-'a']<<endl; 
				
				time-=ot["print"];
				prog[rqf].pop();	
			}
			if(statement=="lock")
			{
				time-=ot["lock"];
				if(lockflag==0)
				{
					lockflag=1;
					prog[rqf].pop();	
				}
				else
				{
					lq.push(rqf);
					lqin=1;	
					break;
				} 
					
			}
			if(statement=="unlock")
			{
				time-=ot["unlock"];
				prog[rqf].pop();
				lockflag=0;
				if(!lq.empty())
				{
					rq.push_front(lq.front());
					lq.pop();
				}			
			}
			if(rtmp[0]=="end")
			{
				time-=ot["end"];
				prog[rqf].pop();
				endflag=1;
				break;	
			}
			stmp=prog[rqf].front();
			rtmp[1].clear();
			stringstream ss(stmp);
			ss>>rtmp[0]>>rtmp[1]>>rtmp[2];
			if(rtmp[1]=="=")
			{
				statement="=";
			}
			else
			{
			if(rtmp[0]=="print")
			statement="print";
			if(rtmp[0]=="lock")
			statement="lock";
			if(rtmp[0]=="unlock")
			statement="unlock";
			if(rtmp[0]=="end")
			statement="end";	
			}		
		}
		if(!endflag&&!lqin)
		{
			rq.push_back(rqf);		
		}
		
	}	
	cout<<endl;	
	}
   return 0;	 	
} 

例题6-2

本题不多说,书中的分析和解法简洁明了。

#include<iostream>
#include<stack>
using namespace std;
const int maxn=1000;
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	int len;
	while(cin>>len&&len)
	{
		int da[maxn];
		for(int i=0;i<len;i++)
		{
			da[i]=i+1;
		}
		int fir;
		while(cin>>fir&&fir)
		{
			int dc[maxn];
			stack<int> st;
			dc[0]=fir;
			for(int i=1;i<len;i++)
			{
				cin>>dc[i];
			}
			int a=0,c=0,flag=1;
			while(c<len)
			{
				if(da[a]==dc[c])
				{
					a++;
					c++;
				}
				else if(!st.empty()&&st.top()==dc[c])
				{
					c++;
					st.pop();
				}
				else if(a<len)
				{
					st.push(da[a]);
					a++;
				}
				else
				{
					flag=0;
					break;
				}
				
			}
			if(flag)	
			cout<<"Yes"<<endl;
			else
			cout<<"No"<<endl;		
		}
		cout<<endl;
	}
	return 0;
} 

例题6-3

本题我采用map存储各个字母矩阵对应的行和列,而本题使用结构体来存行和列,用数字索引代替字母,具有一定的技巧性,而在中间过程的计算中,也直接不存中间过程的名字,只存储计算中间过程中的行和列。而我在中间过程中,将中间过程存储在map中。

#include<iostream>
#include<stack>
#include<map>
#include<string>
#include<sstream>
using namespace std;
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	typedef pair<int,int> Pair;
	map<string,Pair> ma;
	int m;
	cin>>m;
	while(m--)
	{
		string c;
		Pair rc;
		cin>>c>>rc.first>>rc.second;
		ma[c]=rc;
	}
	string s;
	getline(cin,s);
	//cout<<s<<endl;
	while(getline(cin,s))
	{
		//cout<<s<<endl;
		int ans=0,flag=1;
		stack<string> sc;
		for(int i=0;i<s.length();i++)
		{
			if(s[i]==')')
			{
				string a,b;
				a=sc.top();
				sc.pop();
				b=sc.top();
				sc.pop();
				if(ma[a].first==ma[b].second)
				{
					ans+=ma[a].first*ma[a].second*ma[b].first;
					Pair ab;
					ab.first = ma[b].first;
					ab.second = ma[a].second;
					string sab= a+b;
					ma[sab]=ab;
					sc.push(sab);	
				}
				else
				{
					cout<<"error"<<endl;
					flag=0;
					break;
				}
				
			}
			else if(s[i]!='(')
			{
				stringstream ss;	
				ss<<s[i];
				string s1;
				ss>>s1;
				//cout<<s1;
				sc.push(s1);
			}	
		}
		if(flag)
		cout<<ans<<endl;
	}
	
	return 0;
} 

例题6-4

本题目的写法非常经典,技巧很多,建议逐步运行实现。实现链表的方式,和使用指针实现的不一样,但是效率更高。实际上,思想就是在插入某个元素的时候,要保证上一个元素指向自己,而自己要指向下一个元素(可能为0)。实际上,实现了在上一个元素和光标之间的插入操作。

#include<cstdio>
#include<cstring>
const int maxn=100000+5;
int begin,last,pre,next[maxn];
char s[maxn];
int main()
{
	freopen("datain.txt","r",stdin);
	while(scanf("%s",s+1)==1)
	{
		int n=strlen(s+1);
		last=pre=0;
		next[0]=0;
		for(int i=1;i<=n;i++)
		{
			if(s[i]=='[')
			{
				pre=0;
			}
			else if(s[i]==']')
			{
				pre=last;
			}
			else
			{
				next[i]=next[pre];//cur存储的上一个元素的位置。 
				next[pre]=i;//确定上一个元素的下一个元素的位置。 
				if(pre==last) last=i;
				pre=i;
				
			}
		
		}
		for(int i=next[0];i!=0;i=next[i])
		{	
			if(s[i]!=']'&&s[i]!='[')		
				printf("%c",s[i]);
		}
		printf("\n");		
	}
	return 0;
} 

例题6-5(TLE/uDebug测试全通过)

本题我采用了双端链表,写的方式更像我们所学的数据结构那样,但是遭遇TLE,所有uDebug测试数据都通过。现在就不在纠结这道题了。如果要避免TLE,就要在源头上用最简单的数据结构存储,使用更加低级的操作。比如不要使用结构体,输入输出尽量使用C语言操作。我的写法感觉在算法上,已经不能够再精简了。

#include<iostream>
using namespace std;
const int maxn=100000+5;
struct Boxs
{
	long long  left;
	long long  right;
};
int main()
{	
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	Boxs boxs[maxn]; 
	long long m,n,rnd=1;
	while(cin>>n>>m)
	{
		long long  sum=0; 
		//初始化boxs
		boxs[0].left=0;
		boxs[0].right=1;
		boxs[n+1].left=n;
		boxs[n+1].right=0;
		
		for(long long i=1;i<=n;i++)
		{
			boxs[i].left=i-1;
			boxs[i].right=i+1;	
		} 
		while(m--)
		{
			int t;
			cin>>t;
			if(t==1)
			{
				long long x,y;
				cin>>x>>y;
				
				if(boxs[x].right!=y)
				{
					boxs[boxs[x].left].right=boxs[x].right;
					boxs[boxs[x].right].left=boxs[x].left;
					boxs[boxs[y].left].right=x;
					boxs[x].left=boxs[y].left;
					boxs[x].right=y;
					boxs[y].left=x;	
				}
				
			}
			else if(t==2)
			{
				long long x,y;
				cin>>x>>y;
				if(boxs[y].right!=x)
				{
					boxs[boxs[x].left].right=boxs[x].right;
					boxs[boxs[x].right].left=boxs[x].left;
					boxs[boxs[y].right].left=x;
					boxs[x].left=y;
					boxs[x].right=boxs[y].right;
					boxs[y].right=x;	
				}
					
			}
			else if(t==3)
			{
				long long x,y;
				cin>>x>>y;
				if(boxs[x].right!=y&&boxs[x].left!=y)
				{
				long long tmp;
				boxs[boxs[x].right].left=y;
				boxs[boxs[x].left].right=y;
				boxs[boxs[y].right].left=x;
				boxs[boxs[y].left].right=x;	
				tmp=boxs[x].right;
				boxs[x].right=boxs[y].right;
				boxs[y].right=tmp;
				tmp=boxs[x].left;
				boxs[x].left=boxs[y].left;
				boxs[y].left=tmp;	
				}
				else
				{
					if(boxs[x].right==y)
					{
						boxs[boxs[x].left].right=y;
						boxs[boxs[y].right].left=x;	
						boxs[y].left=boxs[x].left;
						boxs[x].right=boxs[y].right;
						boxs[y].right=x;
						boxs[x].left=y;
					}
					else
					{
						boxs[boxs[x].right].left=y;
						boxs[boxs[y].left].right=x;	
						boxs[x].left=boxs[y].left;
						boxs[y].right=boxs[x].right;
						boxs[x].right=y;
						boxs[y].left=x;	
					}
				}
					
			}
			else if (t==4) 
			{
				long long tmp;
				for(long long i=1;i<=n;i++)
				{
					tmp=boxs[i].left;
					boxs[i].left=boxs[i].right;	
					boxs[i].right=tmp;	
				}
					boxs[boxs[0].right].right=n+1;
					boxs[boxs[n+1].left].left=0;
					tmp=boxs[0].right;
					boxs[0].right=boxs[n+1].left;
				 	boxs[n+1].left=tmp;	 	
			}
						
		}
		
			int j=1;
			for(long long i=boxs[0].right;i!=0;i=boxs[i].right)
			{
				if(j%2==1&&boxs[i].right!=0)		
					sum+=i;	
					j++;
			}
			cout<<"Case "<<rnd++<<": "<<sum<<endl;
	}
	return 0;
} 

例题6-6

本题目见书中分析,书中给出两段代码,第一段TLE,第二段AC,但是第一段非常容易理解,第一段可以理解为穷举。

第一段:TLE

#include<cstdio>
#include<cstring>
const int maxn= 20 ;
int s[1<<maxn];//<<位运算,表示1*(2^maxn); 
int main()
{
	int m;
	scanf("%d",&m);
	while(m--)
	{
		int D,I;
	while(scanf("%d%d",&D,&I)==2)
	{
		memset(s,0,sizeof(s));
		int k,n=(1<<D)-1;//n为最大节点编号. 
		for(int i=0;i<I;i++)
		{
			k=1;
			while(true)
			{
				s[k]= !s[k];
				k=s[k]?2*k:2*k+1;//k是变化的,本来k为真,为右边。
				//但是因为提前改变状态,所以k为假在右边。
				if(k>n) break; //循环结束条件。  	
			}
			
		}
		printf("%d\n",k/2);
	}	
	}
	
	return 0;
} 

第二段:AC

#include<cstdio>
#include<cstring>
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	long int D,I;
	int m;
	scanf("%d",&m);
	while(m--)
	{
		while(scanf("%ld%ld",&D,&I)==2)
		{
			long int k=1;
			for(int i=0;i<D-1;i++)
			{
				if(I%2)
				{
					k=k*2;
					I=(I+1)/2;
				}
				else
				{
					k=k*2+1;
					I/=2;
				}	
		    }
		 printf("%d\n",k);	
		}
	
	}
	return 0;
} 

例题6-7

本题就是典型的如果构建一个树,构建后进行宽度遍历(层次遍历)。主要注意对输入的处理方式,利用队列来进行树的宽度遍历。本题使用指针相对而言,比较好理解。
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=1000;
struct Node
{
	bool have_value;
	int v;
	Node *left,*right;
	Node():have_value(false),left(NULL),right(NULL){}
};
bool failed;
Node* root;
char s[maxn]; 
//树节点的数据结构 

//增加节点 
Node* newnode()
{
	return new Node();
}
//构建树 
void addnode(int v,char* s)
{
	int n = strlen(s);
	Node* u = root; 
	for(int i=0;i<n;i++)
	{
		if(s[i]=='L')
		{
			if(u->left==NULL) u->left = newnode();
			u=u->left;
		}
		else if(s[i]=='R')
		{
			if(u->right==NULL) u->right = newnode();
			u=u->right;	
		}
	}
	if(u->have_value) failed=true;
	u->v=v;
	u->have_value=true;	
}
//输入处理 
bool read_input()
{
	failed=false;
	root=newnode();
	for(;;)
	{
		if(scanf("%s",s)!=1) return false;
		if(!strcmp(s,"()")) break;
		int v;
		sscanf(&s[1],"%d",&v);//字符输入可以类比stringstream 
		addnode(v,strchr(s,',')+1);
	}
	return true;
}
//使用队列辅助实现层次遍历(宽度有限遍历)
bool bfs(vector<int> &ans)
{
	queue<Node*> q;
	ans.clear();
	q.push(root);
	while(!q.empty())
	{
		Node* u=q.front();q.pop();
		if(!u->have_value) return false;
		ans.push_back(u->v);
		if(u->left!=NULL) q.push(u->left);
		if(u->right!=NULL)q.push(u->right);
	}
	return true;
} 
//释放空间 
void remove_tree(Node* u)
{
	if(u==NULL) return;
	remove_tree(u->left);
	remove_tree(u->right);
	delete u;
}


int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	while(read_input())
	{
		vector<int> ans;
		if(failed)
		{
			printf("not complete");	
		}
		else if(bfs(ans))
		{
			for(vector<int>::iterator it= ans.begin();it!=ans.end();it++)
			{
				if(it==ans.end()-1)
				{
					printf("%d",*it);
				}
				else
				{
					printf("%d ",*it);
				}
				
			}	
		}
		else
		{
			printf("not complete");
		}
		
		printf("\n");
		remove_tree(root);
		
	}
	return 0;
} 

本书还介绍了其他几种,构建一个树的方法,但是我个人还是认为第一种方法更加简单和易于理解。

现展示如下:

//使用数组+下标的方式实现二叉树。
const int root=1;
void newtree()
{ 
	left[root]=right[root]=0;
	have_value[root]=false;
	cnt=root; 
} 
int newnode()
{
	int u=++cnt;
	left[u]=right[u]=0;
	have_value[u]=false;
	return u;
}
//使用结构体+指针
Node* newnode()
{
	Node * u = &node[cnt++];//将node[cnt++]的地址给u,意思是将u装入到node中,
	//此时并不是直接随机给u一个地址,new Node();node数据的定义肯定是 Node node[maxn]。 
	u->left=u->right=NULL;
	u->have_value=false;
	return u; 
} 
//内存池维护一个空闲列表  
queue<Node*> freenodes;
Node node[maxn];
void init()
{
	for(int i=0;i<maxn;i++)
	{
		freenodes.push(&node[i]);
	}
} 
Node* newnode()
{
	Node *u = freenodes.front();
	freenodes.pop();
	u->left=u->right=NULL;
	u->have_value=false;
	return u;
}
void deletenode(Node* u)
{
	freenodes.push(u);
}
//实际上,如果没有特殊要求,使用new动态申请空间,使用delete释放空间,就如同最开始代码中生成和删除节点的方法。 

例题6-8

本题先通过后序遍历和中序遍历构建二叉树,在通过深度优先遍历,从根到叶子搜索每一条路径,然后选取最小权的路径。数据结构采用的为两个数字lch和rch来存储根的左右子树,构造树和深度优先遍历都采用了递归的手段。下面的代码为书中代码,我补加了注释,便于大家理解。

#include<iostream>
#include<string>
#include<sstream>
#include<algorithm>
using namespace std;

const int maxv=10000+10;
int in_order[maxv],post_order[maxv],lch[maxv],rch[maxv];
int n;
//读入数据函数 
bool read_list(int *a)
{
	string line;
	if(!getline(cin,line)) return false;
	stringstream ss(line);
	n=0;
	int x;
	while(ss>>x)
	{
		a[n++]=x;
	}
	
	return n>0;
}

//递归构建构建树 
int build(int L1,int R1,int L2,int R2)
{
	//L1为中序遍历的开头,R1为中序遍历的结尾
	//L2为后序遍历的开头,R2为后序遍历的结尾
	if(L1>R1) return 0;//判断结束条件。 
	int root = post_order[R2];//循环寻找根位置 
	//根的位置在后序遍历的最后一个。 
	int p = L1;
	while(in_order[p]!=root) p++;
	int cnt = p-L1;//从中序遍历中获得左子树的节点数。 
	lch[root] = build(L1,p-1,L2,L2+cnt-1); 
	//lch[root]存储的为以root为根的左子树的根 
	rch[root] = build(p+1,R1,L2+cnt,R2-1);
	//rch[root]存储的为以root为根的右子树的根 
	return root;
} 

int best,best_sum;
//深度优先遍历。 
void dfs(int u,int sum)
{
	sum+=u;
	if(!lch[u]&&!rch[u])
	//如果以u为根的左子树和右子树都为空,就是枚举出所有的解来比较。 
	{
		if(sum<best_sum||(sum==best_sum&&u<best))
		//如果以u为根的左子树和右子树不为空。 寻找最小的sum 
		{
			best=u;
			best_sum=sum;
		}	
	}
	if(lch[u]) dfs(lch[u],sum);
	if(rch[u]) dfs(rch[u],sum);	
}
//n为节点个数 
int main()
{
	freopen("datain.txt","r",stdin);
	while(read_list(in_order))
	{
		read_list(post_order);
		build(0,n-1,0,n-1);
		best_sum = 1000000000;
		dfs(post_order[n-1],0);//深度优先遍历,给出根的位置。 
		cout<<best<<endl;
	}
	return 0;
} 

例题6-9

一开始对题目理解的不够完全,输出"YES"的条件为各个子天平都要平衡,而天平的权值为子天平权值的和。肯定是要采用递归的模式来进行。我的代码如下,比较好理解。但书中的代码更加精髓,我对书中代码进行了注释,便于理解学习。

我的代码:

#include<iostream>
using namespace std;
int sumwl=0,sumwr=0,flag=1;
void readleft()
{
	int wl,dl,wr,dr;
	cin>>wl>>dl>>wr>>dr;
	sumwl=sumwl+wl+wr;
	if(wl!=0&&wr!=0) 
	{
		if(wl*dl!=wr*dr)
			flag=0;
		return;
	}
	if(wl==0)
	{
		readleft();	
	}
	if(wr==0)
	{
	 	readleft();	
	}
	
}

void readright()
{
	int wl,dl,wr,dr;
	cin>>wl>>dl>>wr>>dr;
	sumwr=sumwr+wl+wr;
	if(wl!=0&&wr!=0)
	{
		if(wl*dl!=wr*dr)
			flag=0;
		return;
		
	} 
	if(wl==0)
	{
		readright();	
	}
	if(wr==0)
	{
	 	readright();	
	}
}


int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	int m;
	cin>>m;
	while(m--)
	{
		int wl,dl,wr,dr;
		int suml,sumr;
		cin>>wl>>dl>>wr>>dr;
		if(wl==0)
		{
			readleft();
		}
		else
		{
			sumwl=wl;
		}
		if(wr==0)
		{
			readright();
		}
		else
		{
			sumwr=wr;
		}
		if(sumwl*dl==sumwr*dr&&flag)
		{
			cout<<"YES"<<endl;
		}
		else 
		{
			cout<<"NO"<<endl;
		}
		if(m!=0)
		{
			cout<<endl;
		}
		sumwl=sumwr=0;
		flag=1;
		
	}
	return 0;
} 

书中代码分析:

首先,使用递归必须明确递归结束的条件,显然题目中结束的条件为W1不为0,则结束左子树的递归,当W2不为0,则结束右子树的递归。而我们感兴趣的是子树是不是平衡的和子树的权值总和,这样才能计算自己是否平衡,所以,书中代码使用b1和b2来确定左右子树是否平衡,使用W1*D1==W2*D2来确定自己是否平衡,而权值则通过引用进行传递W为权值和,W1为左子树的权值,W2为右子树的权值。

#include<iostream>
using namespace std;
bool solve(int &W)
{
	int W1,D1,W2,D2;
	bool b1=true,b2=true;
	cin>>W1>>D1>>W2>>D2;
	if(!W1) b1=solve(W1);
	if(!W2) b2=solve(W2);
	W=W1+W2;
	return b1&&b2&&(W1*D1==W2*D2);
}

int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	int T,W;
	cin>>T;
	while(T--)
	{
		if(solve(W)) cout<<"YES\n";
		else cout<<"NO\n";
		if(T) cout<<"\n";
	}
	return 0;
}
运用递归要明确两个要点,第一个是结束的条件,第二个是需要递归确定的值,比如本题中的权重,如果需要知道总的权值,那么就需要知道左右子树权值之和。那么可以递归下去,先求得最底层的权值,再返回向上,进行计算。

例题6-10(uDebug测试数据通过,UVa RE)

本题我的思路是先将这个二叉树看成完全二叉树。这样我们只要知道层数,那么我们就可以计算出根的位置,在根的左边就根的位置-1,根的右边就根的位置+1。然后输出就可以得到答案。

#include<iostream>
#include<cstring>
using namespace std;
const int maxn=1000000;
int sumres[maxn];
int sumn=1,cnt=1; 
int lf[maxn],rt[maxn],value[maxn];

void build(int root,int n)
{
	int tmpn=n,leftvalue,rightvalue;
	cin>>leftvalue;
	if(leftvalue!=-1)
	{
		tmpn++;
		int left = ++cnt;
		value[left]=leftvalue;
		lf[root]=left;
		if(tmpn>sumn)
		sumn=tmpn;
		build(left,tmpn);	
	}
	tmpn=n;
	cin>>rightvalue;
	if(rightvalue!=-1)
	{
		tmpn++;
		int right = ++cnt;
		value[right]=rightvalue;
		rt[root]=right;
		if(tmpn>sumn)
		sumn=tmpn;
		build(right,tmpn);
	}
	
}


void calres(int root,int code)
{
	sumres[code]+=value[root];
	if(lf[root]!=-1)
	{
		calres(lf[root],code-1);
	}
	if(rt[root]!=-1)
	{
		calres(rt[root],code+1);	
	}
	
	
	
}
	
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	int root,rootvalue,n=1,rnd=1;
	cin>>rootvalue;
	while(rootvalue!=-1)
	{
		memset(lf,-1,sizeof(lf));
		memset(rt,-1,sizeof(rt));
		memset(sumres,0,sizeof(sumres));
		memset(value,0,sizeof(value));
		cnt=1;
		root=cnt;
		value[root]=rootvalue;
		sumn=1;
		build(root,n);
		int len;
		if(sumn>2)
		{
			len=(1<<(sumn-1))+(1<<(sumn-2));
			calres(root,(len/2));
		}
		else if(sumn==2)
		{
			calres(root,2);
		}
		else
		sumres[1]=rootvalue;	
		cout<<"Case "<<rnd++<<":"<<endl;
		int flag=0;
		for(int i=1;i<=len;i++)
		{
			if(sumres[i]!=0)
			{
				if(!flag)
				{
					cout<<sumres[i];
					flag=1;
				}
				else
				cout<<" "<<sumres[i];
			}
		
		}
	cout<<endl<<endl;	
	cin>>rootvalue;	
	}	
	return 0;
} 

例题6-11

本题一开始真不知道怎么做,所以借鉴了书中代码。我给书中的代码写了少许注释,以便于理解。

#include<cstdio>
#include<cstring>
const int len=32;
const int maxn=1024+10;
char s[maxn];
int buf[len][len],cnt;
//本题目两张图在进行输入时候,将结果输入到buf中。
//将图片视为32*32的一个图 ,并使用r和c对每个方框进行编号后
//放入buf中。 
//w表示现在递归中的ch表示的格子个数。 
void draw(const char*s,int &p,int r,int c,int w)
{
	char ch = s[p++];
	if(ch=='p')
	{
		draw(s,p,r,c+w/2,w/2);//1
		draw(s,p,r,c,w/2);//2
		draw(s,p,r+w/2,c,w/2);//3
		draw(s,p,r+w/2,c+w/2,w/2);//4
	}else if(ch=='f')
	{
		for(int i=r;i<r+w;i++)
			for(int j=c;j<c+w;j++)
				if(buf[i][j]==0)
				{
					buf[i][j]=1;cnt++;
				}
	}
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		memset(buf,0,sizeof(buf));
		cnt=0;
		for(int i=0;i<2;i++)
		{
			scanf("%s",s);
			int p=0;
			draw(s,p,0,0,len);
		}
		printf("There are %d black pixels.\n",cnt);
	}
	return 0;
}

例题6-12

本题目主要是对八连块的理解,八连块和连通的数量没关系,只是表示可以上下左右斜上斜下等8个方向进行移动。通过dfs进行遍历,具体代码注释已经写好,可以参考学习。

#include<cstdio>
#include<cstring>
const int maxn=100+5;
char pic[maxn][maxn];
int m,n,idx[maxn][maxn];

void dfs(int r,int c,int id)
{
	if(r<0||r>=m||c<0||c>=n) return;
	//行和列越界判断 
	if(idx[r][c]>0||pic[r][c]!='@') return;
	// idx[r][c]>0 表示已经被遍历过。 
	// 节约时间,快速返回。 
	idx[r][c]=id;
	for(int dr=-1;dr<=1;dr++)
	//dc和dr表示可以朝上下左右移动 
		for(int dc=-1;dc<=1;dc++)
			if(dr!=0||dc!=0) dfs(r+dr,c+dc,id);
			//dr和dc不能同时为0,如果同时为0,则原地不动。 
} 
int main()
{
	while(scanf("%d%d",&m,&n)==2&&m&&n)//接收行和列 
	{
		for(int i=0;i<m;i++) scanf("%s",pic[i]);
		//一行一行的接收,并存在一个字符串数组中。
		//实际上是将输入数据存入pic数组中。 
		memset(idx,0,sizeof(idx));
		int cnt=0;
		for(int i=0;i<m;i++)
			for(int j=0;j<n;j++)
				if(idx[i][j]==0&&pic[i][j]=='@') dfs(i,j,++cnt);
			//每个@进行一次深度优先遍历。idx[i][j]表示还没有被遍历。
			//意味着现阶段还没有加入其他八连块。 
			printf("%d\n",cnt);
	}
	return 0;
}

例题6-13

本题步骤:

1. 先将输入的十六进制转化为二进制图像(先转化为十进制),其中0表示白色,1表示黑色。

2. 因为每个字符图像是一个四连块,将图像进行dfs,并将各个字符图像并从1开始编号。

3. 此时,图中的0,有些为空洞的0,有些为非空洞的0,判断每一个0的是否为空洞0,判断的方法为从元素为0的位置向上、向下、向左、向右搜索第一个大于0的元素,如果上下左右都相等且大于0,那么这个0元素就为空洞元素,否则为非空洞元素(判断是否在字符图像中),对从非空洞元素位置开始dfs,并标记为-1.这样就找到这张图中所有的空洞元素和非空洞元素。空洞元素标记为0.非空洞元素标记为-1.

4. 将空洞元素进行dfs,找到每个空洞元素属于的字符图像,实际上为dfs的边界元素(边界元素就为开始标记的字符图像的编号)。

5. 这样就可以通过每个字符图像的空洞数量来进行输出。

6. 代码有具体注释,可供参考。

#include<iostream>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=10000;
int pic[maxn][maxn],idx[maxn][maxn];
int r,c,bt;
void dfs(int ir,int ic,int id)
{
	if(ir<0||ir>=r||ic<0||ic>=c) return;
	if(idx[ir][ic]>0||pic[ir][ic]!=1) return;
	idx[ir][ic]=id;
	dfs(ir+1,ic,id);
	dfs(ir-1,ic,id);
	dfs(ir,ic+1,id);
	dfs(ir,ic-1,id);	
}
void dfsz(int ir,int ic,int id)
{
	if(ir<0||ir>=r||ic<0||ic>=c) return;
	if(idx[ir][ic]==-1||pic[ir][ic]!=0) return;
	idx[ir][ic]=id;
	dfsz(ir+1,ic,id);
	dfsz(ir-1,ic,id);
	dfsz(ir,ic+1,id);
	dfsz(ir,ic-1,id);	
}

void dfsv1(int ir,int ic,int id)
{
	if(ir<0||ir>=r||ic<0||ic>=c) return;
	if(idx[ir][ic]==id) return;
	if(idx[ir][ic]>0)
	{
		bt=idx[ir][ic];
		return;
	}
	idx[ir][ic]=id;
	dfsv1(ir+1,ic,id);
	dfsv1(ir-1,ic,id);
	dfsv1(ir,ic+1,id);
	dfsv1(ir,ic-1,id);	
}
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	char input[205][55];
	int ans[100],rnd=1;
	char code[]="WAKJSD";
	while(cin>>r>>c&&r!=0&&c!=0)
	{  
		memset(idx,0,sizeof(idx));
		memset(pic,0,sizeof(pic)); 
		memset(ans,0,sizeof(ans));  
		int m=0;
		for(int i=0;i<r;i++) 
		{
			cin>>input[i];
			int tmp,bi[4];
			m=0;
			for(int j=0;j<c;j++)
			{
				memset(bi,0,sizeof(bi));
			//十六进制转化成十进制。
				if(isdigit(input[i][j]))
				{
					tmp=input[i][j]-'0';
				}
				else
				{
					tmp=input[i][j]-'a'+10;
				}
		    //十进制转化成二进制。	
				for(int n=3;n>=0;n--)
				{
					bi[n]=tmp%2;
					tmp=tmp/2;
					if(tmp==0) break;
				}
				for(int n=0;n<4;n++)
				{
					pic[i][m++]=bi[n];
				}
			}
		}
		c = m;//重新生成图片后的列数 
    	int cnt=0;//字符的个数 
		//通过dfs将所有字符进行编号 
		for(int i=0;i<r;i++)
		{
			for(int j=0;j<c;j++)
			{
				if(pic[i][j]==1&&idx[i][j]==0)
				{
					dfs(i,j,++cnt);
				}
			}
		}		
//将pic中非空洞的0标注为-1,保证数空洞的时候不会数错。
//搜索每一个为非空洞的0,并进行dfs进行-1.
//判断每一个0点 
	for(int i=0;i<r;i++)
	{
		for(int j=0;j<c;j++)
		{
			if(idx[i][j]==0)
			{
				int up=-1,down=-1,left=-1,right=-1;
				//向上搜索
				for(int m=i;m>=0;m--)
				{
					if(idx[m][j]>0)
					{
						up=idx[m][j];
						break;
					}
						
				}
				//向下搜索
				for(int m=i;m<r;m++)
				{
					if(idx[m][j]>0)
					{
						down=idx[m][j];
						break;
					}
						
				}
				//向左搜索
				for(int m=j;m>=0;m--)
				{
					if(idx[i][m]>0)
					{
						left=idx[i][m];	
						break;
					}
					
				}
				//向右搜索
				for(int m=j;m<c;m++)
				{
					if(idx[i][m]>0)
					{
						right=idx[i][m];
						break;
					}
						
				}
				//如果上下左右都相等的话,为空点。
				//否则不为空洞点。 
				if(!(left==right&&left==up&&up==down&&left!=-1))
				{
					dfsz(i,j,-1);
				}
			  		
			}							
		}
	}
	
//		for(int i=0;i<r;i++)
//	{
//		for(int j=0;j<c;j++)
//		{
//			cout<<idx[i][j]<<" ";
//		}
//		cout<<endl;
//	}	
//	
	
	
		//数空洞的数量
			int hcnt=-1;
				for(int i=0;i<r;i++)
				{
					for(int j=0;j<c;j++)
					{
						if(idx[i][j]==0)
						{
							bt=0;
							dfsv1(i,j,--hcnt);
							ans[bt]++;
						}
					}
				}
				
		char res[100];
		
		for(int i=1;i<=cnt;i++)
		{
			res[i]=code[ans[i]];
		}
		sort(res+1,res+cnt+1);
		cout<<"Case "<<rnd++<<": ";
		for(int i=1;i<=cnt;i++)
		{
			cout<<res[i];
		}
		 cout<<endl;
		 
			
	}	
	return 0;
}

例题6-14

本题目将书中的代码补全,并进行了注释。书中进行判断是否前进的inside()函数,我不知道其中的含义,也没有写这个函数,UVa还是通过了。这里其实要注意的就是两个点1是存从初始状态到某个点的最短路长度,2是存BFS中的父节点。实际上,在进行BFS采用队列进行辅助。

#include<iostream>
#include<string>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=100;
const char* dirs="NESW";
const char* turns="FLR";
int d[maxn][maxn][4];
int has_edge[maxn][maxn][4][3];
int r0,c0,r1,c1,r2,c2,dir; 

struct Node
{
	int r,c,dir;
	Node(int r1=0,int c1=0,int dir1=0)
	{
		r=r1;
		c=c1;
		dir=dir1;
	}
	
}; 

Node p[maxn][maxn][4];

int dir_id(char c)
{
	return strchr(dirs,c)-dirs;
	//查找字符串dirs中首次出现字符c的位置
}
int turn_id(char c)
{
	return strchr(turns,c)-turns;
}
//根据当前状态和转弯方式计算出来的后续状态。 
const int dr[]={-1,0,1,0};
const int dc[]={0,1,0,-1};
//dc实际上表示为列上面的走向。dr为行上面的走向 

Node walk(const Node& u ,int turn)
{
	int dir=u.dir;
	if(turn==1) dir=(dir+3)%4;//左转 
	if(turn==2) dir=(dir+1)%4;//右转 
	return Node(u.r+dr[dir],u.c+dc[dir],dir);
}


//从后到前的输出 
void printf_ans(Node u)
{
	vector<Node> nodes;
	for(;;)
	{
		nodes.push_back(u);
		if(d[u.r][u.c][u.dir]==0) 
		{
			break;
		}
		u = p[u.r][u.c][u.dir];
	}
	//寻找父节点 
	nodes.push_back(Node(r0,c0,dir));
	int cnt = 10;
	for(int i=nodes.size()-1;i>=0;i--)
	{
		if(cnt%10==0) printf(" ");
		printf(" (%d,%d)",nodes[i].r,nodes[i].c);
		if(++cnt%10==0)
		printf("\n");
	}
	if(nodes.size()%10!=0) printf("\n");
}


//has_edge是一个四维数组 
void solve()
{
	queue<Node> q;
	memset(d,-1,sizeof(d));
	Node u(r1,c1,dir);//起点 
	d[u.r][u.c][u.dir]=0;
	//初始状态到(r,c,dir)的最短路径 
	q.push(u);//然后将u压入栈内 
	while(!q.empty())//层次遍历是否结束 
	{
		Node u = q.front();
		q.pop();
		if(u.r==r2 && u.c==c2)//表示如果u已经到达终点 
		{
			printf_ans(u);//输出 
			return;
		}
		for(int i=0;i<3;i++)//3个方向 
		{
			Node v = walk(u,i);
			if(has_edge[u.r][u.c][u.dir][i]&&d[v.r][v.c][v.dir]<0)
			//has_edge表示这种前进方式是否能够进入。d[v.r][v.c][v.dir]表示还没有到达这个点.
			{
				d[v.r][v.c][v.dir]=d[u.r][u.c][u.dir]+1;
				p[v.r][v.c][v.dir]=u;
				q.push(v);
			} 
		}
	}
	printf("  No Solution Possible\n");
}


int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	string name;
	char cdir;
	while(cin>>name&&name!="END")
	{
		cout<<name<<endl;
		memset(has_edge,0,sizeof(has_edge));
		cin>>r0>>c0>>cdir>>r2>>c2;
		dir=dir_id(cdir);
		r1=r0+dr[dir];
		c1=c0+dc[dir];
		int tmpr,tmpc;
		while(cin>>tmpr&&tmpr)
		{
			cin>>tmpc;
			char tmp;
			int tmpdt1i,tmpdt2i;		
			while(cin>>tmp&&tmp!='*')
			{
				if(tmp=='N'||tmp=='E'||tmp=='W'||tmp=='S')
				{
					tmpdt1i=dir_id(tmp);	
				}
				if (tmp=='L'||tmp=='R'||tmp=='F')
				{
					tmpdt2i=turn_id(tmp);
					has_edge[tmpr][tmpc][tmpdt1i][tmpdt2i]=1;		
				}
				
			}
		}
		solve();
	}	
	return 0;
} 

例题6-15

本题使用DFS进行拓扑排序,是一个经典的写法。其思想就是,在一个无环有向图中,先找到拓扑排序中的最后一个点,最后一个点满足的条件就是它的执行不会影响到其他任务。也就是说别人只会影响它,它不会影响别人。迭代进行就可以求得拓扑排序。代码中有注释,但是如果算法还是理解不了,可以百度DFS+拓扑排序。很多网页提供更加详尽的解释。

#include<cstdio>
#include<cstring>
const int maxn=100+5;
//c数组,0表示从未访问过,1表示已经访问并递归过它的子孙。
//-1表示正在访问。
int c[maxn]; 
int topo[maxn],t,n;
int G[maxn][maxn]; 
bool dfs(int u)
{
	c[u]=-1;
	for(int v=1;v<=n;v++)
	{
		if(G[u][v])
		//采用邻接矩阵存储图,表示u有指向v的边,表示u完成后v才能完成。 
		{
			if(c[v]<0) return false;//判断是否有环的存在 
			else if (!c[v]&&!dfs(v)) return false;
			//如果v已经被访问过,则退出。
			//如果没有访问,就进行深度遍历,进行访问。 
		}
		
	}
	c[u]=1;topo[--t]=u;
	return true;
}
bool toposort()
{
	t=n;
	memset(c,0,sizeof(c));
	for(int u=1;u<=n;u++)
	{
		if(!c[u])//没有进行访问的才进行访问 
		{
			if(!dfs(u)) return false;
		}
	}
	return true;
}
	
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	int m;
	while(scanf("%d%d",&n,&m)&&n)
	{
		memset(G,0,sizeof(G));
		while(m--)
		{
			int i,j;
			scanf("%d%d",&i,&j);
			G[i][j]=1;
		}	
		toposort();
		for(int i=0;i<n;i++)
		{
			printf("%d",topo[i]);
			if(i<n-1)
			printf(" ");
		}
		printf("\n");		
	}
	
	return 0;
}

例题6-16

通过存在欧拉回路的条件进行判断,再使用dfs来判断是否连通。

#include<iostream>
#include<string>
#include<cstring>
using namespace std;
const int maxn=30;
int G[maxn][maxn],visit[maxn],od[maxn],id[maxn];
void dfs(int s)
{
	visit[s]=-1;
	for (int i=0;i<maxn;i++)
	{
		if(G[s][i]==1&&!visit[i])
		{
			dfs(i);
		}
	}
	
	
}
int check()
{
	int flag=1;
	for(int i=0;i<maxn;i++)
	{
		if(visit[i]==0)
		{
			flag=0;
			return flag;
		}
	}
	return flag;
}
int eulerroot()
{
	int start=-1,end=-1,first;
	for(int i=0;i<maxn;i++)
	{
		int tmp;
		if(od[i]!=0)
		{
			first=i;
		}
		tmp=od[i]-id[i];
		if(tmp==1)
		{
			if(start==-1)
				start=i;
			else
				return -1;	
		}
		else if(tmp==-1)
		{
			if(end==-1)
				end=i;
			else
				return -1;
		}
		else if(tmp!=0)
		{
			return -1;
		}	
	}
	if(start==-1)
	{
		return first;
	}
	return start;
}
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	int m;
	cin>>m;
	while (m--)
	{	
		memset(G,0,sizeof(G));
		memset(od,0,sizeof(od));
		memset(id,0,sizeof(id));
		memset(visit,-1,sizeof(visit));
		int n,start;
		cin>>n;
		while(n--)
		{
			string tmp;
			cin>>tmp;
			int r,c;
			r=tmp[0]-'a';
			c=tmp[tmp.length()-1]-'a';
			G[r][c]=1;
			visit[r]=0;
			visit[c]=0;
			od[r]++;
			id[c]++;
		}
			start=eulerroot();
			if(start==-1)
				cout<<"The door cannot be opened."<<endl;
			else
			{
				dfs(start);	
				int flag=check();
				if(flag)
				{
					cout<<"Ordering is possible."<<endl;
				}
				else
					cout<<"The door cannot be opened."<<endl;	
			}	
	}
	return 0;
} 

例题6-17

本题目的难点还是在对输入数据的处理上,难点并不在数据结构,书中的代码也易于理解。

#include<cstdio>
#include<cctype>
#include<cstring>
const int maxn =200+10;
int n;
char buf[maxn][maxn];
void dfs(int r,int c)
{
	printf("%c(",buf[r][c]);
	if(r+1<n&&buf[r+1][c]=='|')//n为buf的总行数,这句话表示有子树 
	{
		int i=c;
		while(i-1>=0&&buf[r+2][i-1]=='-') i--;//找到---的左边界 
		while(buf[r+2][i]=='-'&&buf[r+3][i]!='\0')
		{
			if(!isspace(buf[r+3][i])) dfs(r+3,i);
			i++;
		}
	}
	printf(")");
} 

void solve()
{
	n=0;
	for(;;)
	{
		fgets(buf[n],maxn,stdin);
		if(buf[n][0]=='#') break; else n++;
	}
	printf("(");
	if(n)
	{
		for(int i=0;i<strlen(buf[0]);i++)
			if(buf[0][i]!=' ')
			{
				dfs(0,i);break;
			}
	}
	printf(")\n");
}
int main()
{
	//freopen("datain.txt","r",stdin);
	int T;
	fgets(buf[0],maxn,stdin);
	sscanf(buf[0],"%d",&T);
	while(T--) solve();
	return 0;
}

例题6-20

本题书中数相当重要使用了两次BFS,具体详细的分析和解释可以参考。

点击打开链接

写的很详细。我将他的代码贴在下面。另外本题采用BFS采用邻接表的形式,数据结构中可以使用邻接表和邻接矩阵存图,具体将会在第六章的总结中呈现。

#include<cstdio>
#include<vector>
#include<queue>
#include<cstring> 
using namespace std; //min()函数 
#define max 100000
#define inf 0x7fffffff
typedef struct ver{
    int num, color;//边的另一端的节点编号 和 颜色 
    ver(int n,int c):num(n),color(c){}
}Ver;
int n,m,a,b,c;
int d[max],res[max];//d记录每个点到终点的最短距离 res记录最短路的颜色
bool vis[max],inqueue[max];//vis每个节点是否被访问过 inqueue标记节点是否加入了队列,防止重复加入 
vector<Ver> edge[max];//邻接表记录图 
void bfs(int start,int end){
    memset(inqueue,0,n);
    memset(vis,0,n);
    int u,v,c;
    queue<int> q;
    q.push(start);
    if(start==0){//用于正向bfs 
        memset(res,0,sizeof(int)*n);
        while(!q.empty()){
            u=q.front();q.pop();vis[u]=1;
            if(u==n-1)return;
            int minc=inf,len=edge[u].size();
            for(int i=0;i<len;i++)if(!vis[v=edge[u][i].num] && d[u]-1==d[v])minc=min(edge[u][i].color,minc);//获取所有路径中最小的颜色
            for(int i=0;i<len;i++)if(!vis[v=edge[u][i].num] && d[u]-1==d[v] && edge[u][i].color==minc && !inqueue[v])q.push(v),inqueue[v]=1; //若有多组颜色相同,且未入队,则将其入队 
            int index=d[0]-d[u];//获得当前步数对应的下标
            if(res[index]==0)res[index]=minc;
            else res[index]=min(res[index],minc);//获取最小颜色 
        }
    }//用于反向bfs 构建层次图,找最短路 
    else while(!q.empty()){
        u=q.front();q.pop();vis[u]=1;
        for(int i=0,len=edge[u].size();i<len;i++)if(!vis[v=edge[u][i].num] && !inqueue[v]){ 
            d[v]=d[u]+1; //一定是头一次入队,这通过inqueue保证 
            if(v==0)return; //找到起点,退出 
            q.push(v);//如果不是起点,就把这个点入队 
            inqueue[v]=1;//入队标记 
        }
    }
}
int main(){
    while(scanf("%d%d",&n,&m)==2){
        for(int i=0;i<n;i++)edge[i].clear();
        memset(d,-1,sizeof(int)*n);d[n-1]=0;//注意初始化的细节
        while(m--){
            scanf("%d%d%d",&a,&b,&c);
            if(a!=b){ //排除自环
                edge[a-1].push_back(ver(b-1,c));
                edge[b-1].push_back(ver(a-1,c));
            }
        }
        bfs(n-1,0);//反向bfs
        bfs(0,n-1);//正向bfs 
        printf("%d\n%d",d[0],res[0]);
        for(int i=1;i<d[0];i++)printf(" %d",res[i]);
        printf("\n");
    }
}

例题6-1

本题就是一个栈的使用,一定要注意输入的格式问题。

#include<iostream>
#include<stack>
#include<string>
using namespace std;
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	int n;
	cin>>n;
	getchar();
	while(n--)
	{
		stack<char> sc;
		while(sc.size()) sc.pop();
		string s;
		getline(cin,s);
		bool flag = true;
		for(int i=0;i<s.length();i++)
		{
			if(s[i]=='('||s[i]=='[')
			{
				sc.push(s[i]);
			}
		   	else if (!sc.empty()&&((s[i]==']'&&sc.top()=='[')||(s[i]==')'&&sc.top()=='(')))
			   {
			   		sc.pop();
			   }
			else
			{
				flag=false;
				break;
			}
			  	
	   }
		if(sc.empty()&&flag)
			cout<<"Yes"<<endl;
		else
			cout<<"No"<<endl;
	}
	return 0;
} 

习题6-2

本题第一眼看是树,实际上和树关系不大,根据题意,我们可以知道在叶子节点分别可以编号成十进制的0-2^n,n为树深度,那么再根据输入的编码和树中给出的顺序进行一一对应,这样就输入的编码转化为10进制,直接就能得出答案。

#include<iostream>
#include<string>
#include<cstring>
using namespace std;
const int maxn=1000;
int main()
{
	int n,rnd=1;
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	while(cin>>n&&n)
	{
		cout<<"S-Tree #"<<rnd++<<":"<<endl;
		int res[maxn],tim[n+1],decode[maxn];
		memset(res,-1,sizeof(res));
		memset(decode,-1,sizeof(decode));
		for(int i=1;i<=n;i++)
		{
			string s;
			cin>>s;
			tim[s[1]-'0']=i;
		}
		for(int i=0;i< 1<<n;i++)
		{
			char tmp;
			cin>>tmp;
			res[i]=tmp-'0';
		}
		getchar();//接收回车 
		int m;
		cin>>m;
		while(m--)
		{
			for(int i=1;i<=n;i++)
			{
				char tmp;
				cin>>tmp;
				decode[tim[i]]=tmp-'0';		
			}
			int sum=0;
			for(int i=1;i<=n;i++)
			{
				
				sum+=decode[i]*(1<<(n-i));;		
			}
			cout<<res[sum];	
		}
		cout<<endl<<endl;
		
	}
	return 0;
} 

习题6-3

可以参照例题6-10进行,对于树而言,三种遍历方式非常重要,我也将会在第六章进行总结。

#include<iostream>
#include<string>
#include<cstring>
using namespace std;
const int maxn=1000;
int lch[maxn];
int rch[maxn];
string preorder,inorder;
int build (int L1,int R1,int L2,int R2)//L1R1为中序遍历,L2R2为先序遍 
{
	if(L1>R1) return -1;
	int root = preorder[L2]-'A';
	int p=L1;
	while(inorder[p]-'A'!= root) p++;
	int cnt=p-L1;//计算左子树的数量 
	lch[root]=build(L1,p-1,L2+1,L2+cnt-1);
	rch[root]=build(p+1,R1,L2+cnt+1,R2);
	return root;
}
void postorder(int root)
{

	if(lch[root]!=-1) postorder(lch[root]);
	if(rch[root]!=-1) postorder(rch[root]);
	char tmp=root+'A';
	cout<<tmp;
	
}
int main()
{
 	//freopen("datain.txt","r",stdin);
 	string s;
 	while(getline(cin,s))
	{
		memset(lch,-1,sizeof(lch));
		memset(rch,-1,sizeof(rch));
		int index=s.find(" ",0);
		preorder=s.substr(0,index);
		inorder=s.substr(index+1,s.length()-index-1);
		int len1=preorder.length();
		int len2=inorder.length();
		int root=build(0,len2-1,0,len1-1);
		postorder(root);
		cout<<endl;
 		preorder.clear();
 		inorder.clear();
	}
	return 0;
} 

习题6-4

本题目使用一次BFS就行。难度不大。为了使用队列方便,除了使用(x,y)坐标表示一个格子外,也将棋盘的左下角开始从左到右,从下到上由0-63进行编号。

#include<iostream>
#include<string>
#include<cstring>
#include<queue>
using namespace std;
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	string s;
	while(getline(cin,s))
	{
		int board[9][9],vis[9][9];
		queue<int> q;
		int x = s[0]-'a'+1;
		int y = s[1]-'0';
		memset(board,-1,sizeof(board));
		memset(vis,-1,sizeof(vis));
		board[x][y]=0;
		vis[x][y]=0;
		int index= 8*(y-1)+x-1;
		q.push(index);
		while(!q.empty())
		{
			index= q.front();
			y = index/8+1;
			x = index%8+1;
			q.pop();
			if(x+2<=8&&y+1<=8&&vis[x+2][y+1]==-1)
			{
				board[x+2][y+1]=board[x][y]+1;
				vis[x+2][y+1]=0;
				index=8*(y)+x+1;
				q.push(index);
			}
			if(x+1<=8&&y+2<=8&&vis[x+1][y+2]==-1)
			{
				board[x+1][y+2]=board[x][y]+1;
				vis[x+1][y+2]=0;
				index= 8*(y+1)+x;
				q.push(index);
			}
			if(x+2<=8&&y-1>=1&&vis[x+2][y-1]==-1)
			{
				board[x+2][y-1]=board[x][y]+1;
				vis[x+2][y-1]=0;
				index= 8*(y-2)+x+1;
				q.push(index);
		
			}
			if(x+1<=8&&y-2>=1&&vis[x+1][y-2]==-1)
			{
				board[x+1][y-2]=board[x][y]+1;
				vis[x+1][y-2]=0;
				index= 8*(y-3)+x;
				q.push(index);
		
			}
			if(x-2>=1&&y-1>=1&&vis[x-2][y-1]==-1)
			{
				board[x-2][y-1]=board[x][y]+1;
				vis[x-2][y-1]=0;
				index= 8*(y-2)+x-3;
				q.push(index);
			}
			if(x-2>=1&&y+1<=8&&vis[x-2][y+1]==-1)
			{
				board[x-2][y+1]=board[x][y]+1;
				vis[x-2][y+1]=0;
				index= 8*(y)+x-3;
				q.push(index);
			}
			if(x-1>=1&&y-2>=1&&vis[x-1][y-2]==-1)
			{
				board[x-1][y-2]=board[x][y]+1;
				vis[x-1][y-2]=0;
				index= 8*(y-3)+x-2;
				q.push(index);	
			}
			if(x-1>=1&&y+2<=8&&vis[x-1][y+2]==-1)
			{
				board[x-1][y+2]=board[x][y]+1;
				vis[x-1][y+2]=0;
				index= 8*(y+1)+x-2;
				q.push(index);
			}
				
		}
		
		 x = s[3]-'a'+1;
		 y = s[4]-'0';
		 cout<<"To get from "<<s[0]<<s[1]<<" to "<< s[3]<<s[4]<<" takes "<<board[x][y]<<" knight moves."<<endl;
			
	} 
	return 0;
}

习题6-5

本题目采用BFS,但是不仅要考虑到达每个点的最短距离,还要考虑到到达这个点的时候,剩余的可以连续穿越障碍的步数。这样到达可能出现到达某点的时候,离起点的距离不同,剩余可以连续跨越障碍的不通,但是根据BFS的运行规则,第一个到达终点的肯定就是最短路径的。程序如下,注意队列的清空。

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int maxn=100;
struct Node
{
	int x,y,d,k;//k存储还能前进几步
	Node(){};
	Node(int x1,int y1,int d1,int k1):x(x1),y(y1),d(d1),k(k1){};
};

int row,col,maxk,board[maxn][maxn],vis[maxn][maxn][maxn];
int dir[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
queue<Node> q;
bool legal (int x,int y)
{
	return x>=0&&x<row&&y>=0&&y<col;
}
int  bfs()
{
	while(!q.empty())
	{
		Node tmp = q.front();
		q.pop();
		if(tmp.x==row-1&&tmp.y==col-1)
			return tmp.d;
		for(int i=0;i<4;i++)
		{
			int newx= tmp.x+dir[i][0];
			int newy= tmp.y+dir[i][1];
			if(legal(newx,newy))
			{
				int newk= board[newx][newy]?tmp.k-1:maxk;
				if(newk>=0&&vis[newx][newy][newk]==-1)
				{
					vis[newx][newy][newk]=0;
					Node NewNode(newx,newy,tmp.d+1,newk);
					q.push(NewNode);
				}
				
			}
		}
	}
	return -1;	
}
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	int n;
	cin>>n;
	while(n--)
	{
		cin>>row>>col>>maxk;
		memset(vis,-1,sizeof(vis));
		for(int i=0;i<row;i++)
		{
			for(int j=0;j<col;j++)
			{
				cin>>board[i][j];
			}
		}
		while(q.size())q.pop();
		Node begin(0,0,0,maxk);
		q.push(begin);
		int res=bfs();
		cout<<res<<endl;
		
	}	
	return 0;
} 

习题6-6

本题目的思路主要是要通过遍历,知道每个叶子节点的深度。具体思路见点击打开链接。通过学习别人的代码可以采用ios::sync_with_stdio(false);来提升读取速度。

可以使用auto自动推断类型的变量声明,来减少代码的输入量,但是auto在我的机器上测试的有问题。

我将他的代码进行了稍微的注释,以便大家学习。

#include<iostream>
#include<string>
#include<map>
#include<algorithm>
using namespace std;

string line;
map<long long,int> base;
int sum;
void dfs(int depth, int s,int e)
{
	if(line[s]=='[')
	{
		int p=0;
		for(int i=s+1;i<e;i++)
		{
			if(line[i]=='[') p++;
			if(line[i]==']') p--;
			if(p==0&&line[i]==',')
			//通过逗号,和括号标记P来进行判断自己所在的深度。 
			{
				dfs(depth+1,s+1,i-1);
				dfs(depth+1,i+1,e-1);
			}
		}
	}
	else 
	{
		long long w=0;
		for(int i=s;i<=e;++i) 
		w=w*10+line[i]-'0';//将字符转化为数字,并且是从高位从低位读取。 
		++sum,++base[w<<depth];
		//并且记录总的节点数量。 
		//自己的值不变,整个天平能够达到的权重。 
	}
}
int main()
{
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	ios::sync_with_stdio(false);
	int T;
	cin>>T;
	while(T--)
	{
		cin>>line;
		base.clear();
		sum=0;
		dfs(0,0,line.size()-1);
		int maxn=0;
		for (map<long long,int>::iterator it = base.begin();it!=base.end();it++)
		maxn=max(maxn,it->second);//找到能够达到相同权重且值不变的节点数量。 
		cout<<sum-maxn<<endl;
	}
} 


习题6-7

本题目理解好题意后,发现并没有涉及到图和树,对于树而言,我们可以看成是一个一对多的关系,图时一个多对多的关系。而对于题目中的一个trans,

可能有多个输入和输出,采用树或者图的方式处理显然不是太好。那么设置一个结构体存储trans。trans包含输出的place和需要从这个place,token的数量。

结构体中的数据采用in[place]=token数量的方式存储这些信息,再进行处理。

#include<iostream> 
#include<map>
#include<cstring>
using namespace std;
const int maxn = 100;
struct trans
{
	int in[maxn],out[maxn];
};
int findtrans(trans T[],int token[],int NT,int NP)
{

	for (int i=0;i<NT;i++)
	{	
		int flag=0;
		for (int j=1;j<=NP;j++)
		{
			if(token[j]<T[i].in[j])
			{
				flag=1;
				break;	
			}
		}
		if(!flag)
			return i;
	}
	return -1;
}
		
int main()
{
	ios::sync_with_stdio(false);
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);    
	int NP,NT,NF,token[maxn],rnd=1;
	trans T[maxn];
	while (cin>>NP&&NP)
	{
		memset(token,0,sizeof(token));
		for(int i=1;i<=NP;i++)
		{
			cin>>token[i];
		}
		cin>>NT;
		for(int i=0;i<NT;i++)
		{
			for(int j=0;j<=NP;j++)
			{
				T[i].in[j]=0;
				T[i].out[j]=0;
			}
			int tmp;
			while(cin>>tmp&&tmp)
			{
				if(tmp<0)
				{
					T[i].in[-tmp]++;
				}
				else
				{
					T[i].out[tmp]++;
				}
			
			}
		}
		cin>>NF;
		int flag=0,dead=0;
		for(int i=1;i<=NF;i++)
		{
			int ex = findtrans(T,token,NT,NP);
			if (ex!=-1)
			{
				for (int i=1;i<=NP;i++)
				{
					if(T[ex].in[i]!=0)
					token[i]-=T[ex].in[i];
					if(T[ex].out[i]!=0)
					token[i]+=T[ex].out[i];
				}
				
				dead++;
			}
			else
			{
				flag=1;
				break;
			}	
		}
		if(!flag)
		{
			cout<<"Case "<<rnd++<<": still live after "<<NF<<" transitions"<<endl;
		}
		else
		{
			cout<<"Case "<<rnd++<<": dead after "<<dead<<" transitions"<<endl;	
		}
		cout<<"Places with tokens:";
		for(int i=1;i<=NP;i++)
		{
			if(token[i]>0)
			cout<<" "<<i<<" ("<< token[i] <<")";
		}
		cout<<endl<<endl;	
	}
	
	return 0;
}

习题6-8

本题就是一个DFS,主要递归要用的变量和结束的条件。输出格式相当烦人。

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn=1000;
int G[maxn][maxn],res[maxn],lenres=0,route[maxn],routlen=0;

int  allblack(int sr,int er,int sc,int ec)
{
	for(int i=sr;i<=er;i++)
	{
		for(int j=sc;j<=ec;j++)
		{
			if(G[i][j]!=1)
			{
				return 0;
			}
		}
	}
	return 1;	
}
void solve(int sr,int er,int sc,int ec,int depth,int index,int sum)
{
	int flag;
	if(sr>er||sc>ec)
	{
		return ;
	}
	if(sr==er&&sc==ec)
	{
		flag=allblack(sr,er,sc,ec);
		if(flag)
		{
		sum=sum+index*pow(5,depth);
		res[lenres++]=sum;
		}
		return;
	}
	flag=allblack(sr,er,sc,ec);
	if(flag)
	{
		sum=sum+index*pow(5,depth);
		res[lenres++]=sum;
		return;
	}
	else
	{
		
		int midr = sr+(er-sr)/2;
		int midc = sc+(ec-sc)/2;
		if(depth>=0)
		sum=sum+index*pow(5,depth);
		solve(sr,midr,sc,midc,depth+1,1,sum);
		solve(sr,midr,midc+1,ec,depth+1,2,sum);
		solve(midr+1,er,sc,midc,depth+1,3,sum);
		solve(midr+1,er,midc+1,ec,depth+1,4,sum);	
	}

	
}
void locate(int &sr,int &er,int &sc,int &ec,int index)
{
		int midr = sr+(er-sr)/2;
		int midc = sc+(ec-sc)/2;
	if(index==1)
	{
		er=midr;
		ec=midc;
		
	}
	if(index==2)
	{
		er=midr;
		sc=midc+1;
		
	}
	if(index==3)
	{
		sr=midr+1;
		ec=midc;
	}
	if(index==4)
	{
		sr=midr+1;
		sc=midc+1;
	}
	
}



void fillone(int n)
{
	int sr=0,er=n-1,sc=0,ec=n-1; 
	for (int i=0;i<routlen;i++)
	{
		locate(sr,er,sc,ec,route[i]);
	}
	for(int i=sr;i<=er;i++)
		for(int j=sc;j<=ec;j++)
			G[i][j]=1; 
}


int main()
{
	
	//freopen("datain.txt","r",stdin);
	//freopen("dataout.txt","w",stdout);
	int n,rnd=1;
	while(cin>>n&&n)
	{
		if(rnd!=1)
		{
			cout<<endl;
		}
		cout<<"Image "<<rnd++<<endl;
		lenres=0;
		if(n>0)
		{
			char c;
			c=getchar();
			for(int i=0;i<n;i++)
			{
				for(int j=0;j<n;j++)
				{	
					c=getchar();
					G[i][j]=c-'0';
				}	
				c=getchar();
			}				
			solve(0,n-1,0,n-1,-1,0,0);		
			sort(res,res+lenres);
			for(int i=0;i<lenres;i++)
			{
				cout<<res[i];
				if((i+1)%12==0)
				cout<<endl; 
				else if(i!=lenres-1)
				cout<<" ";
			}
			if(lenres%12!=0)
			cout<<endl;
			cout<<"Total number of black nodes = "<<lenres<<endl;	
		}
		else
		{
			n=-n;
			memset(G,0,sizeof(G));
			int tmp;
			while(cin>>tmp&&tmp!=-1)
			{
				memset(route,0,sizeof(route));
				routlen=0;
				while (tmp!=0)
				{
					route[routlen++] = tmp%5;
					tmp = tmp/5;
				}
				fillone(n);		
			}
			for(int i=0;i<n;i++)
			{
				for(int j=0;j<n;j++)
				{
					if(G[i][j]==0)
					cout<<".";
					else
					cout<<"*";
				}
				cout<<endl;
			}	
		}
	}
	return 0;
} 


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值