程序设计思维与实践第五六周实验小结

第五周
作业A 最大矩形问题
给定一个直方图,求解该直方图中最大矩形面积。
要求矩形的面积我们需要矩形的长度和宽度。由于矩形的宽度被直方柱的高度所限定,所以我们的做法是求出任一高度下满足条件的最大矩形面积并进行比较,从而得到直方图中最大矩形面积。
要求某一高度下最大矩形面积我们就要知道满足条件的矩形最大长度是多少。什么时候不满足条件?高度小于给定高度时不满足条件。所以,对于目标直方柱,我们在将其高度作为矩形宽度的同时,需要找到其左边最后一个和右边第一个高度比其小的直方柱位置。两个位置之间的间隔即为最大长度。我们就可以得到某一高度下最大矩形面积。
如何找到这两个位置,我们使用单调栈:当栈顶元素的高度小于比较元素的高度时,我们将比较元素的位置压入栈,同时我们得到第一个比比较元素小的位置;反之,我们弹出栈顶元素重新比较,直至栈顶元素的高度小于比较元素的高度。这样,我们就可以得到一定顺序下每一个高度的左边界。针对这道题,我们进行左右两次操作,我们就可以得到想要的两个位置,从而解决题目。
本题实现过程中困难主要在于一开始对单调栈原理的不理解,在熟悉了单调栈的工作过程后本体的实现便很容易了。但该题还有一个需要注意的问题在于数据大小。这就告诉我们在做题的时候除了需要理清题意,也需要在意题中的细节,由题目所给的数据要求给出合理的数据类型也是实验成功的必要环节。

#include<iostream>
#include<stack> 

using namespace std;

int main(){
	int n;
	while(cin>>n&&n!=0){
		long long int a[n+2],b[n+2],c[n+2];
		a[0]=0;a[n+1]=0; 
		for(int i=1;i<n+1;i++){
			cin>>a[i];
		}	
	stack<int> s;
	for(int i=0;i<n+2;i++){
	    while(!s.empty()&&a[s.top()]>=a[i]){
	    	s.pop();
		}
		if(s.empty()) b[i]=0;
		else          b[i]=s.top();
		s.push(i);	
	}
	for(int i=n+1;i>=0;i--){
	    while(!s.empty()&&a[s.top()]>=a[i]){
	    	s.pop();
		}
		if(s.empty()) c[i]=0;
		else          c[i]=s.top();
		s.push(i);	
	}
	long long int squ=0,max=0;
	for(int i=1;i<=n;i++){
		squ = a[i]*(c[i]-b[i]-1);
		if(squ>max) max=squ;
	}
	cout<<max<<endl;
	}
}

B题 城市资产运算
给定城市数量和每一个城市的资产价值,接下来进行多次操作。每一次操作选择多个城市对其资产进行相同改变。输出操作后每一个城市的价值。
如果每次操作都对范围内的城市逐一进行资产值改变,那么基于实验所给数据范围必定超时。因此我们需要改变实验思路。根据题意,每一次操作都选择多个城市对它们的资产值进行同样的操作,也就是说,这些城市资产值的差值在改变前后是不变的,变的只是未被选择部分与已选择部分间的差值。如果我们从差值入手,将范围内每个城市的资产值改变转化为选择部分与未选择部分资产值差值的改变,我们就可以将每次的m次操作转为2次操作,大大减少实验所需时间。
同样,本题也需要根据题目数据范围选择合适的数据类型及输入输出格式。会做的题目因为细节出错无疑是很可惜的。

#include<iostream>
#include<stdio.h>

using namespace std;

int main(){
	int n,q;
	scanf("%d%d",&n,&q);
	long long int a[n],b[n];
	scanf("%lld",&a[0]);
	b[0]=a[0];
	for(int i=1;i<n;i++){
		scanf("%lld",&a[i]);
		b[i]=a[i]-a[i-1];
	}
	for(int i=0;i<q;i++){
		int l,r,c;
		cin>>l>>r>>c;
		b[l-1]=b[l-1]+c;
		b[r]=b[r]-c;
	}
	a[0]=b[0];
	printf("%lld",a[0]);
	for(int i=1;i<n;i++){
		a[i]=a[i-1]+b[i];
		printf(" %lld",a[i]);
	}
}

C 题 平衡字符串
给定一个只由‘Q’,‘W’,‘E’,‘R’四种字符组成的字符串。给出最小替换长度使得替换后字符串中四种字符数量相等。
首先判断字符串是否已经相等,相等后就无需采取任何措施。如果不相等,我们就开始寻找要替换的字符串:首先我们将起始位置作为第一个判断目标。计算剩余位置需要多少字符才能达到平衡,如果字符数量超过替换长度,证明替换不成功,新增替换字符串右边第一个元素;若替换长度大于等于字符串数量且差值为4的倍数,证明该长度下可以使字符串平衡,我们记录下此时长度并删除此时替换字符串中第一个元素。从头遍历到尾,我们就得到了每次成功时替换字符串的长度,如果在遍历时再加以判定,我们就能一次性得到最小替换长度。
基于这种算法,由于我们需要计算剩余位置需要多少字符才能达到平衡,我们就要得知每一次判断下剩余位置四种字符的数量。如果每次都要重新计算,无疑会大大增加实验时间。那么如何优化呢?由于我们每次对替换字符串的操作都是要么右增一,要么左减一。也就是说,剩余位置中字符的数量变化其实就只与增或减的字符有关。那么我们在起始得到原字符串中四种字符的数量后,我们只需根据增或减的元素类别对相应的数值进行一次加减操作即可。由此便极大缩减了实验时间,避免超时。

#include<iostream>
#include<string.h>
#include<stdio.h>

using namespace std;
char s[1000000];
int main(){
	gets(s);
	int L=0,R=0,min=strlen(s);
	int i=0,sum1=0,sum2=0,sum3=0,sum4=0;
	while(i<min){
			if(s[i]=='Q') sum1++;
			else if(s[i]=='W') sum2++;
			else if(s[i]=='E') sum3++;
			else if(s[i]=='R') sum4++;
			i++;
		}
	if(sum1==sum2&&sum1==sum3&&sum1==sum4){
		cout<<0<<endl;
		return 0;
	}
	int a=2;
	while(R<strlen(s)){
		int total=R-L+1;
		if(a==1){
			if(s[L-1]=='Q') sum1++;
			else if(s[L-1]=='W') sum2++;
			else if(s[L-1]=='E') sum3++;
			else if(s[L-1]=='R') sum4++;
		}
		else if(a==2){
			if(s[R]=='Q') sum1--;
			else if(s[R]=='W') sum2--;
			else if(s[R]=='E') sum3--;
			else if(s[R]=='R') sum4--;
		} 
		else if(a==3){
			if(s[L-1]=='Q') sum1++;
			else if(s[L-1]=='W') sum2++;
			else if(s[L-1]=='E') sum3++;
			else if(s[L-1]=='R') sum4++;
			if(s[R]=='Q') sum1--;
			else if(s[R]=='W') sum2--;
			else if(s[R]=='E') sum3--;
			else if(s[R]=='R') sum4--;
		}
		int max=sum1;
		if(max<sum2) max=sum2;
		if(max<sum3) max=sum3;
		if(max<sum4) max=sum4;
		int free=4*max-sum1-sum2-sum3-sum4;
		free=total-free;
		if(free>=0&&free%4==0){
			int len=R-L+1;
			if(len<min) min=len;
			L++;
			a=1;
			if(L>R){
				R++;
				a=3;
			}
		}
		else {
				R++;
				a=2;
		}
	}
	cout<<min<<endl;
	}

D题 滑动窗口问题
给定数列与窗口,输出窗口从左向右的滑动过程中,每次窗口内数的最大值和最小值。
数据范围限制使得我们不能简单遍历得到每一次的最大最小值。对于这种局部数据的最值问题,我们采用单调队列来实现:最小值的实现:在向右移动的过程中,如果新增元素大于队尾元素,直接入队,反之抛出队尾元素直至新增元素大于队尾再入队。这样,我们就把每次必不可能是最小值的元素排除出去,每一次的队首元素即为当前窗口最小元素,最大值的实现与最小值基本相同。注意:压入队列的元素不一定都是有效元素,为了满足“当前窗口”的实验限定,对于不属于窗口的元素,我们同样要将它从队列中删除。

#include<iostream>
#include<stdio.h>
#include<queue> 

using namespace std;

int main(){
	int n,k;
	cin>>n>>k;
	int a[n];
	for(int i=0;i<n;i++){
		scanf("%d",&a[i]);
	}	
	deque<int> q;
	for(int i=0;i<n;i++){
	    while(q.size()>0&&q.front()<=i-k){
	    	q.pop_front();
		}
		while(q.size()>0&&a[q.back()]>a[i]){
	    	q.pop_back();
		}
		q.push_back(i);
		if(i==k-1){
			cout<<a[q.front()];
		}
		else if(i>k-1){
			cout<<" "<<a[q.front()];
		}	
	}
	cout<<endl;
	q.clear();
	for(int i=0;i<n;i++){
	    while(q.size()>0&&q.front()<=i-k){
	    	q.pop_front();
		}
		while(q.size()>0&&a[q.back()]<a[i]){
	    	q.pop_back();
		}
		q.push_back(i);
		if(i==k-1){
			cout<<a[q.front()];
		}
		else if(i>k-1){
			cout<<" "<<a[q.front()];
		}	
	}
	}

第六周 限时模拟
牌型分类问题
给定扑克牌的花色数和每一种花色的牌数,从这些扑克牌中取走两张作为两张固定牌,再从剩下的牌中任取三张组成“一手牌”。按照题目给定的“一手牌”分类,给出每种类别的数量。
每一种牌只有两种状态:选中和未选中。为了选中多张牌中的三张,我们可以对所有未被选中的牌进行两次操作:不选,对下一张牌操作;选中,对下一张牌操作,直到选择了三张牌,我们就得到了所有选择三张牌的情况。根据每一次我们得到的“一手牌”,我们按照题目要求判断其种类。由于“低序号”优先,所以我们要由后向前判断,从而得到该牌属于哪一类别,最终得到所有“一手牌”组合中,每种牌型的数量。

#include<iostream>
#include<algorithm>
using namespace std;
int A,B;
struct P{
	int a;
	int b;
	bool operator<(const P &p) const{
	if(a!=p.a) return a<p.a;
	else       return b<p.b;}
}; 
P p[100];
int flag[100]={0};
P choice[5];
void dfs(int n,int *a,int j,P *choice){
    if(n==5){
    	int s[25]={0};
    	int q[4]={0};
    	for(int i=0;i<5;i++){
    		s[choice[i].a]++;
    		q[choice[i].b]++;
		}
		sort(s,s+25);
		sort(q,q+4);
		int k=9;
		if(s[24]==3){
			if(s[23]==2){
				k=5;
			}
			else k=7;
		}
		else if(s[24]==2){
			if(s[23]==2){
				k=6;
			}
			else k=8;
		}
		else if(s[24]==4){
			k=4;
		}
		else {
			P choice1[5];
			for(int i=0;i<5;i++){
				choice1[i].a=choice[i].a;
				choice1[i].b=choice[i].b;
			}
			sort(choice1,choice1+5);
			int pan=1;
			for(int i=1;i<5;i++){
				if(choice1[i].a-choice1[i-1].a!=1){
					pan=0;
				}}

			if(pan==1){
				k=2;
			}	
		}
		if(q[3]==5){
			if(k==2)	k=1;
			else        k=3;
			}
		a[k-1]++;
		return;
	}
	if(j>=A*B){
		return;
	}
	if(flag[j]==1){
		dfs(n,a,j+1,choice);
	}
	else{
	dfs(n,a,j+1,choice);
	choice[n]=p[j]; 
	dfs(n+1,a,j+1,choice);	
}}
int main(){ 

    cin>>A>>B;
    for(int i=0;i<B;i++){
		for(int j=0;j<A;j++){
			p[i*A+j].a=j;
			p[i*A+j].b=i;
		}
	}
	int a1,b1,a2,b2;
	cin>>a1>>b1>>a2>>b2;
	choice[0]=p[b1*A+a1];
	choice[1]=p[b2*A+a2];
	flag[b1*A+a1]=1;
	flag[b2*A+a2]=1;
	int n=2;
	int cla[9]={0};
	dfs(n,cla,0,choice);
	for(int i=0;i<9;i++){
		cout<<cla[i]<<" ";
	}
  }

第六周作业
A题 最长路问题
给定N台电脑及其连接方式,输出每台电脑到其他电脑的最大长度。
每台电脑到其他电脑的最大长度一定与图中相距最远的两个顶点有关。因此我们需要先找到相距最远的两个顶点。为此,我们需要先找到其中一个顶点。如何寻找?从图中任意一点出发,与其相距最远的点一定是这两点其中之一。(与第一句话原理相同)那么我们由图中任意一点出发,找到相距最远两点中的一点,并由此得到另一点。再分别计算其余点到这两点的距离,比较得出最大值,便是最终结果。

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<vector>

  using namespace std;
  
  struct Edge{
  	int v,w;
  	Edge(){}
  	Edge(int v1,int w1){
		v=v1;
		w=w1;
	}
  };
  vector<Edge> e[20000];
  int reach[20000];
  
  void dfs(int u,int pre,int len,int *reach,int &num,int &s)
{
    if(len>num)
		num=len, s=u;
	for(int i=0;i<e[u].size();i++){
		 int v=e[u][i].v, w = e[u][i].w;
         if(v==pre)
		 	continue;
         dfs(v,u,len+w,reach,num,s);
         reach[v]=max(reach[v],len+w);
     }
}
   int main(){
   	int N,u,w;
   	while(scanf("%d",&N)!=EOF){
   		for(int i=2;i<=N;i++){
   			scanf("%d%d",&u,&w);
   			Edge a(u, w);
			e[i].push_back(a);
			Edge b(i, w);
			e[u].push_back(b);
		   }
		for(int i=0;i<=N;i++){
			reach[i] = 0;}
		int num=0,s=0;
		dfs(1,-1,0,reach,num,s);
		dfs(s,-1,0,reach,num,s);
		dfs(s,-1,0,reach,num,s);
		for(int i=1;i<=N;i++){
			e[i].clear();
		    cout<<reach[i]<<endl;
		}
	   }
   } 

B题 并查集问题
给定学生数量,小团体数量和小团体中学生人员,找出与某同学接触过的同学。
该题是一个简单的并查集问题。根据每个小团体的人员交集将小团体合并为一个个大团体,从而得到某同学所在大团体的人数。对于每一个小团体,我们将其中的成员归并到这个团体中,如果该成员同时还属于另一个团体,我们将这两个团体进行归并。具体操作过程就是对于每一个团体我们设置一个代表元素,当两个团体进行合并时,我们改变其中一个团体的代表元素,从而实现合并。合并后,找到某同学所在的团体,我们得到团体中同学的数量,即为最终答案。
并查集问题实质上是一种树结构,找到每一个元素的团体实际上也就是从孩子节点找到树的根节点。如果树很高,那么找团体的过程很耗时间。为避免这一情况,我们可以对每棵树进行路径压缩,使得团体中每一个成员的父节点都直接是树的根节点,由此减少运算时间。

#include<iostream>

using namespace std;

int pre[50000];

int find(int root){
	int child,tmp;
	child=root;
	while(root!=pre[root]){
		root=pre[root];
	}
	while(child!=root){
		tmp=pre[child];
		pre[child]=root;
		child=tmp;
	}
	return root;
}
 
int main(){
	int m,n;
	while(cin>>n>>m&&(n!=0||m!=0)){
		int root1,root2;
		for(int i=0;i<n;i++){
			pre[i]=i;
		}
		for(int i=0;i<m;i++){
			int num;
			cin>>num;
			int p;
			cin>>p;
			int q;
			for(int i=1;i<num;i++){
				cin>>q;
				root1=find(p);
				root2=find(q);
				if(root1!=root2){
					pre[root1]=root2;
				}
			}
		}
		int sum=0;
		for(int i=0;i<n;i++){
			if(find(i)==find(0)){
				sum++;}
		}
		cout<<sum<<endl;
	}
}

C 题 灌水问题
给定n块农田与两种灌溉方式:直接灌溉与引水灌溉。不同灌溉方式耗费不同,不同农田间引水耗费也不同。给出给全部农田灌水的最小耗费。
既然整体耗费最小,我们就可以每次选择当前选项中耗费最小的一项。将所有灌溉选项由小到大排列,每次选出最小的一项。判断该项(或该两项)是否被浇水。有则忽略,无则浇水。这实际上便是最小生成树算法,而这样设计的正确性在于最后生成的图一定是一个连通图,也即是每个农田一定会被直接灌溉或引水灌溉(为了实现这一点,除农田外我们还要增加一个水源点)。计算每次浇水耗费之和,即得到最小耗费。
原理简单,但实现起来很崩溃。不得不说,操作符重载出错是真滴难受。

#include<iostream>
#include<algorithm>

  using namespace std;
  
 
 struct Edge{
  	int u,v,w;
  	bool operator<(const Edge& e){
  		return w<e.w;
	  }
  };
  int b[50000];
  Edge e[50000];
  
  int find(int x){
	if(b[x] == x)
		return x;
	b[x] = find(b[x]);
	return b[x];	
}
   int main(){
   	int n,w;
   	cin>>n;
	int k=0; 
	for(int i=0;i<=n;i++){
   		b[i]=i;
	   }
   	for(int i=1;i<=n;i++){
   		e[k].u=0;
   		e[k].v=i;
   		cin>>e[k].w;
   		k++;
	   }
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>w;
			if(i>j)
			{
			e[k].u=i;
   		    e[k].v=j;
   		    e[k].w=w;
   		    k++;
			}
		}
	}
	sort(e,e+k);
	int min=0;
	for(int i=0;i<k;i++){
		int e1=e[i].u;
		int e2=e[i].v;
		if(find(e1)!=find(e2)){
			min=min+e[i].w;
			b[find(e2)]=find(e1);
		}
	}
	cout<<min<<endl;
   } 

D题 数据中心
给出一个n节点的网络图,求出最优树结构传输图,使最大传输时间最小。
这道题实现思路与C题基本相同,最大传输时间最小,我们不妨从传输时间最小的路径开始构图。当所有节点都被连通起来之后,我们选择的最后一条路径的时间一定是最小的最大传输时间。

#include <iostream>
#include <stdio.h>
#include <algorithm>

  using namespace std;
  
 struct Edge{
  	int u,v,w;
  	Edge(){}
  	Edge(int u1,int v1,int w1){
		u=u1;
		v=v1;
		w=w1;
	}
  	bool operator<(const Edge& e){
  	  return w<e.w;
	  }
  };
  int b[50005];
  Edge e[100005];
  
  int find(int x){
	if(b[x]==x)
		return x;
	b[x] = find(b[x]);
	return b[x];	
}
   int main(){
   	int n,m,r;
   	cin>>n>>m>>r;
	for(int i=0;i<=n;i++){
   		b[i]=i;
	   }
	int u,v,w;
	int k=0;
   	for(int i=0;i<m;i++){
   		cin>>u>>v>>w;
   		Edge x(u,v,w);
   		e[k]=x;
   		k++;
	   }
	sort(e,e+m);
	int max=0,num=0;
	for(int i=0;i<m;i++){
		int e1=e[i].u;
		int e2=e[i].v;
		if(find(e1)!=find(e2)){
			if(max<e[i].w){
			max=e[i].w;}
			b[find(e2)]=find(e1);
			num++;
			if(num==n-1) break;
		}
	}
	cout<<max<<endl;
   } 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值