2018年安徽省程序设计大赛题目

2018年安徽省程序设计大赛参考题解

水平有限错误难免。

A: 数7

时间限制:1 s

题目描述:

​ 求整数序列A 中位置L 到R中间一共有多少个7,(每一个位上的7和能被7整除的次数)

输入

第一行是T(T<=50)

然后是T行 L R (L<R<1 e 5)

输出

输出7的个数

样例输入
2
1 10
47 50
样例输出
2
3
提示

第二组数据解释 47 中含有一个7 49含有2个7 所以输出3

题解

暴力搜索

#include<bits/stdc++.h>
using namespace std;
int t;
int l,r,ans;

int slove(int x){
	int ans1=0,tmp=x;
	while(x!=0){
		int k=x%10;
		if(k==7)ans1++;
		x/=10;
	}
	while(tmp%7==0){
		ans1++;
		tmp/=7;
	}
	return ans1;
}
int main(){
    scanf("%d",&t);
    while(t--){
    	ans=0;
        scanf("%d%d",&l,&r);
        for(int i=l;i<=r;i++){
			ans+=slove(i);
        }
        printf("%d\n",ans);
    } 
    return 0;
}

B: 编译错误

时间限制:3 s

题目描述:

​ 在程序员编写程序的时候,通常会引用其他文件,而引用的文件也会引用其它的头文件。但是出现循环引用的现象编译时便会报错。例如A引用了B,B引用了C,C引用了A,那么就产生了循环引用(Circular reference)。考虑另外一个情况,A引用了B和C,B引用D,C引用D,虽然D被引用了两次,但是没有出现循环引用 。

输入

第一行是一个整数T,代表测试数据的组数。每组数据中第一行是一个整数n,代表有多少个引用关系。接下来n行每行有2个字符串a,b,用空格分隔,代表a引用了b。其中T<=50, n<=10^5,每个字符串的长度不超过100。

输出

​ 共T行。若不会产生编译错误则输出Passed,否则输出Failed。

样例输入
2
8
client.cpp client.h
client.h server.h
server.cpp server.h
server.h common.h
client.h common.h
common.cpp common.h
common.h gtest.h
common.h glog.h
4
work.cpp client.cpp
client.cpp server.cpp
server.cpp adhoc.cpp
adhoc.cpp work.cpp
样例输出
Passed
Failed
题解:

​ 如果能够拓扑排序说明不会产生编译错误,如果不能拓扑排序说明出现了循环引用。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
struct edge{
	int next,to;
};
edge e[maxn];

int t,n,cnt,ind[maxn],head[maxn];
string a,b;
map<string,int> mp;

void add_edge(int u,int v){
	cnt++;
	e[cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}

bool  topsort(){
	int num=0;
	queue<int>q;
	for(int i=1;i<=n;i++){
		if(ind[i]==0){
			q.push(i);
		}
	}
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].to;
			ind[v]--;
			if(ind[v]==0){
				q.push(v);
			}
		}
		num++;
	}
	if(num==n)return true;
	else return false;
}
int main(){
	scanf("%d",&t);
	while(t--){
		int tot=0,u,v;
		cnt=0;
		memset(head,0,sizeof(head));
		memset(ind,0,sizeof(ind));
		memset(e,0,sizeof(struct edge)*maxn);
		mp.clear();
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			cin>>a>>b;
			if(mp.find(a)==mp.end()){
				mp.insert(pair<string,int>(a,++tot));
				u=tot;
			}
			else u=mp[a];
			if(mp.find(b)==mp.end()){
				mp.insert(pair<string,int>(b,++tot));
				v=tot;
			}
			else v=mp[b];
			add_edge(v,u);
			ind[u]++;
		}
		if(topsort())printf("Passed\n");
		else printf("Failed\n");
	}
	return 0;
}

C: 做操的时候要排好队

时间限制:3 s

题目描述:

同学们在做早操时,应该按照身高从低到高排好队。但是总是有人不好好排队,老师在审查时会对没有排好的队伍扣除一定的分数。扣的分数被定义为,找到三个人Ai,Aj,Ak,其中i<j<k,分数为max(0,(Ai-Aj))+max(0,(Aj-Ak))。找到一组i,j,k使这个分数最大即是扣除的分数。

输入

第一行是一个整数T,代表测试数据的组数。每组数据中第一行是一个整数n,代表有一共有多少个人。第二行共有n个整数,代表n个人的身高。其中1<=T<=50,3<=n<=10^6 每个数的大小不超过1000。

输出

​ 共T行。扣除的分数。

样例输入
2
4
150 160 170 180
4
160 150 170 180
样例输出
0
10
题解:

​ 该题是寻找一个i,j,k,满足(i<j<k)但i,j,k并不一定连续,如果我们枚举中间数j,从2到n-2,对于每一次枚举,我们都需要对i,k分别枚举,时间复杂度为O(n2),肯定不能通过。我们其实在枚举过程中重复了很多计算,对于每一个j,我们的目的是寻找i<j的a[i]最大值,k>j的a[k]最小值。

​ 我们可以预先处理,用两个数组mx[i],mn[i],mx[i]表示i之前a[i]的最大值(不包括a[i]),mn[i]表示以i之后a[i]的最小值。然后我们直接枚举max(mx [i]-a[i],0)+ max(a[i]-mn[i]),时间复杂度为O(n);

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int mx[maxn],mn[maxn];
int t,n;
int a[maxn];

int main(){
	scanf("%d",&t);
	while(t--){
		int ans=0;
		memset(mx,0,sizeof(mx));
		memset(mn,0,sizeof(mn));
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
		}
		mx[1]=a[1],mn[n]=a[n];
		for(int i=2;i<=n-1;i++){
			mx[i]=max(mx[i-1],a[i-1]);
		}
		for(int i=n-1;i>=2;i--){
			mn[i]=min(mn[i+1],a[i+1]);
		}
		for(int i=2;i<=n-1;i++){
			ans=max(max(mx[i]-a[i],0)+max(a[i]-mn[i],0),ans);
		}
		printf("%d\n",ans);	
	}
	
	return 0;
}

D: 判重

时间限制:3 s

题目描述:

小七初始有n个数的样本集。现在又新加了m个数,他想知道这m个数是否已经在这n个数之中了。但是他判断是否有重复的方式和常人有些不同。对于样本集内的任意一个数x,他先将x二进制的第2, 5, 7, 10, 13, 14, 17, 18位取反,生成新的8个数。例如x是7(00111),那么第一个数就是5(00101),第二个数是23(10111)…依次类推。然后将他表格上这8个数的位置全部变为1(初始整个表格全部为0)。判断重复时,若一个数x产生的8个数,在表格内全部为1,就认为这个数x和样本集内的数有重复,否则认为没有重复

输入

输入T,代表T组数据。每组数据初始输入n, m。代表n个样本以及m个需要判断的数。接下来两行,第一行是n个样本,第二行m个需要判断的数,样本在前。对于这m个数,每个数输出yes 或者 no。m组数据相互独立,即这m个数都不会新加入到样本集内。其中T<=50 ,n,m<1e5 。

输出

​ 输出一行,共m个字符串,重复输出yes,否则输出no,空格隔开

样例输入
1
2 2
2 1
3 2
样例输出
no yes
题解:

按位取异或加入集合s,再判重。

#include<bits/stdc++.h>
using namespace std;
int a[8]={2,5,7,10,13,14,17,18};
int t,n,m;
set<int>s;
int main(){
	scanf("%d",&t);
	while(t--){
		s.clear();
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++){
			int x;
			scanf("%d",&x);
			for(int j=0;j<=7;j++){
				int y=x^(1<<(a[j]-1));
				s.insert(y);
			}
		}
		for(int i=1;i<=m;i++){
			int x;
			scanf("%d",&x);
			int flag=1;
			for(int j=0;j<=7;j++){
				int y=x^(1<<(a[j]-1));
				if(s.find(y)==s.end()){
					flag=0;
					break;
				}
			}
		if(flag){
			printf("yes ");
		}
		else printf("no ");
	}
	}
	return 0;
}

E: 最长上升字串

时间限制:3 s

题目描述:

给定n个整数,对其进行m次查询。每次查询是一个范围l到r,求出l到r的最长上升连续子串。上升连续子串的定义为一个连续的子串且严格递增。

输入

第一行是一个整数T,代表测试数据的组数。每组数据中第一行是一个整数n,m,代表有一共有n个人,m个查询。第二行共有n个整数,接下来m行是m次查询,每行两个整数l,r。

输出

​ 共T行,每行m个整数,代表最长上升连续字串。其中T<=50,n,m<1e5,每个数的大小不超过1e9。

样例输入
1
4 2
3 2 4 5
1 3
1 4
样例输出
2
3
题解:

​ 最长连续上升子串,很容易想到动态规划求解。但本题数据量很大,用动态规划无论是时间还是空间可能都过不去。区间最大值,可以用线段树求解,而且不用更新操作。

​ 线段树维护三个值,lv是区间左端点开始的最长上升连续值,rv是以区间右端点结束的最长上升连续值,mv是该区间的最长上升连续值,pushup时分类讨论,当a[mid]<a[mid+1]时,我们可以知道该区间中间两个点是递增的。这是线段树的区间合并操作,如果你有兴趣的话,可以试一试
hdu-3308

本题就是这题的简化版。

#include<bits/stdc++.h>
using namespace std;
const int maxn=100005;
using namespace std;

struct node{
	int l,r;
	int lv,rv,mv;
}; 

node tree[maxn*4+5];
int t,n,m,a[maxn];

void pushup(int i,int l,int r){
	int mid=(l+r)>>1;
	if(tree[i<<1].lv==mid-l+1&&a[mid]<a[mid+1]){
		tree[i].lv=tree[i<<1].lv+tree[i<<1|1].lv;
	}
	else tree[i].lv=tree[i<<1].lv;
	if(tree[i<<1|1].rv==r-mid&&a[mid]<a[mid+1]){
		tree[i].rv=tree[i<<1].rv+tree[i<<1|1].rv;
	}
	else tree[i].rv=tree[i<<1|1].rv;
	tree[i].mv=max(tree[i<<1].mv,tree[i<<1|1].mv);
	if(a[mid]<a[mid+1]){
		tree[i].mv=max(tree[i].mv,tree[i<<1].rv+tree[i<<1|1].lv);
	}
}
void build(int i,int l,int r){
	tree[i].l=l;
	tree[i].r=r;
	if(l==r){
		tree[i].lv=tree[i].rv=tree[i].mv=1;
		return ;	
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	pushup(i,l,r);
}

int query(int i,int x,int y){
	if(tree[i].l>=x&&tree[i].r<=y){
		return tree[i].mv;
	}
	int mid=(tree[i].l+tree[i].r)>>1;
	int mx=0;
	if(x<=mid){
		mx=max(mx,query(i<<1,x,y));
	}
	if(y>mid){
		mx=max(mx,query(i<<1|1,x,y));
	}
	if(a[mid]<a[mid+1]){
		mx=max(mx,min(tree[i<<1].rv,mid-x+1)+min(tree[i<<1|1].lv,y-mid));
	}
	return  mx;
}
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		memset(a,0,sizeof(a));
		memset(tree,0,sizeof(struct node)*4*maxn);
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
		}
		build(1,1,n);
		while(m--){
			int x,y;
			scanf("%d%d",&x,&y);
			printf("%d\n",query(1,x,y));
		} 	
	}
	return 0;
}

F: 雄伟的城堡

时间:3 s

题目描述: 在一个群岛上,有一个富可敌国的大富翁。他打算在这个群岛上建造一个最大城堡,也就是群岛上最大的岛屿。

输入 :

​ 第一行是一个整数T,代表测试数据的组数。每组数据中第一行是两个整数n,m,代表地图的大小。接下来n行每行共m个整数。0代表海洋,1代表陆地。其中T<=50,n,m<=200 。

输出:

​ 共T行,最大的面积。

样例输入
1
5 5
0 1 1 0 0
1 1 0 0 0
0 0 1 1 0
0 1 1 1 1 
0 0 1 1 0
样例输出
8
题解:f

​ dfs或bfs求最大连通块

#include<bits/stdc++.h>
using namespace std;
const int maxn=205;
int t,n,m,ans,area;
int g[maxn][maxn];
bool vis[maxn][maxn];

void dfs(int x,int y){
    if(x<1||x>n||y<1||y>m)return ;
    if(vis[x][y]||!g[x][y])return ;
    vis[x][y]=true;
    area++;
    dfs(x-1,y);
    dfs(x+1,y);
    dfs(x,y-1);
    dfs(x,y+1);
}

int main(){
    scanf("%d",&t);
    while(t--){
        memset(vis,false,sizeof(vis));
        memset(g,0,sizeof(g));
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                scanf("%d",&g[i][j]);
            }
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(!vis[i][j]&&g[i][j]){
                    area=0;
                    dfs(i,j);
                    ans=max(area,ans);
                }
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

G: 然后打5

时间:5s

题目描述:

​ 《炉石传说》是一款考验技(shen)术(chou)的电子游戏,即使你没有玩过这个游戏也没有关系。《炉石传说》里有一张萨满卡牌叫做连环爆裂,它的效果是等概率的造成3-6点伤害。而游戏获胜的条件是将对方英雄的血量全部打掉。例如对方英雄还有5点生命值,你有1张连环爆裂,那么你获胜的概率为0.5。在本题中你无需考虑法力值消耗。

输入

​ 第一行是一个整数T,代表测试数据的组数。接下来的T行每行有2个整数n,h,n代表你一共可以释放的连环爆裂的个数,h代表敌方英雄的血量。其中T<=50,n<=5000,h<=30000。

输出

​ 共T行,每行是一个小数,保留到小数点后6位。

样例输入

1
1 5
样例输出
0.500000
题解

由于是等概率造成伤害,每次打出卡牌造成的伤害p(3)=1/4,p(4)=1/4,p(5)=(1/4),p(6)=1/4;dp[ i ] [ j]表示i张牌时血量为j的答案。对于每次一的(n,h)都等于(n-1,h-3)+(n-1,h-4)+(n-1,h-5)+(n-1,h-6),递归求解可行,但n达到5000,h达到30000,所以可以使用递推。递推一定要处理好边界。

时间复杂度小于O(t+4 * 3000 * 50000)。

#include<bits/stdc++.h>
using namespace std;
const int maxn=5000;
const int maxh=30000;
int t,n,h;
double dp[maxn+5][maxh+5];
int main(){
    scanf("%d",&t);
    for(int i=1;i<=maxn;i++){
        for(int j=0;j<=3*i&&j<=maxh;j++){
            dp[i][j]=1.000000;
        }   
    }
    dp[1][4]=0.750000,dp[1][5]=0.5000000,dp[1][6]=0.25000000;
    for(int i=2;i<=maxn;i++){
        for(int j=3*i+1;j<=maxh;j++){
		 for(int k=3;k<=6;k++){
				if(j-k>=0)dp[i][j]+=dp[i-1][j-k]*0.25;
          }
        }
    }
    while(t--){
        scanf("%d%d",&n,&h);
        printf("%.6lf\n",dp[n][h]);
    }
    return 0;
}


H: 运货卡车

时间:5 s

题目描述:

​ 在一个运输公司中有很多型号完全一样的卡车,每个卡车都可以装载重量为w的货物。有若干个需要运输的货物an,每个货物ai都有一个质量mi。这个运输公司对于这些货物的装载方式策略是,每一次尽可能装更多质量的货物,在有多种可以装载最多质量的货物的方式时,会选择货物下标字典序最小的一组。例如货物的质量为4 3 2 1,卡车能够装载的质量为5时,第一次会选择4 1而不是2 3 。

输入

​ 第一行是一个整数T,代表测试数据的组数。每组样例中,第一行有两个整数n,w,代表有n个货物,每个卡车可以装载质量w的货物。接下来一行有n个数字,代表每个货物的质量。其中T≤20,n,w≤1000。每个货物的质量不会超过w。

输出

共T行,输出所需要的卡车数量。

样例输入
2
4 4
3 3 3 3
4 4
2 2 2 2
样例输出
4
2
题解:

​ 多重01背包,价值与容量相等,每次计算以后将字典序最小的物品进行标记。

这样时间复杂度最差有O(n3),可能还需要加bitset优化背包。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
const int inf =0x3f3f3f3f;
int n,w,t;
int dp[maxn][maxn],a[maxn],ans,num;
bool vis[maxn];
 
int main(){
	scanf("%d",&t);
	while(t--){
		ans=0,num=0;
		memset(vis,true,sizeof(vis));
		scanf("%d%d",&n,&w);
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
		}
		while(num<n){	
			ans++;
			memset(dp,0,sizeof(dp));
		for(int i=n;i>=1;i--){
			for(int j=0;j<=w;j++){
				dp[i][j]=dp[i+1][j];
				if(j>=a[i]&&vis[i]){
					dp[i][j]=max(dp[i][j],dp[i+1][j-a[i]]+a[i]);
				}
			}
		}
		int cur_v=w;
		for(int i=1;i<=n;i++){
			if(i==n&&cur_v>=a[i]&&vis[i]){
				vis[i]=false;
				num++;
				break;
			}
			if(cur_v<=0)break;
			if(cur_v-a[i]>=0&&dp[i][cur_v]==dp[i+1][cur_v-a[i]]+a[i]&&vis[i]){
			vis[i]=false;
				num++;
				cur_v=cur_v-a[i];
			}
		}
	
	}
	printf("%d\n",ans);
}
	
	return 0;
}

I: 最大矩形框

时间:5 s

题目描述:

​ 有一个01的大矩阵,找到一个最大由1围城的矩形框的面积。

输入

​ 第一行是一个整数T,代表测试数据的组数。每组样例中,第一行有两个整数n,m,代表有大矩阵的大小,接下来是一个由01组成的大矩阵。其中T<=10,n,m<=200。

输出

​ 最大由1围成的矩形框的面积。

样例输入
1
5 5
0 1 0 1 0
1 1 1 1 0
1 1 0 1 1
1 1 1 1 1
0 1 1 1 1
样例输出
12
题解

​ 枚举矩形框的上下界,在从左往右扫描。我们可以用数组sum[i] [j].x存储第i行j列的数前面连续多少1,

sum[i] [j].y上面连续多少个1,计算时直接使用。

#include<bits/stdc++.h>
using namespace std;
const int maxn=205;
struct node{
	int x,y;
};
int t;
int a[maxn][maxn];
node sum[maxn][maxn];
int n,m,ans;
int main(){
	scanf("%d",&t);
	while(t--){
		ans=0;
		scanf("%d%d",&n,&m);
		memset(sum,0,sizeof(struct node));
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				scanf("%d",&a[i][j]);
				if(a[i][j]){
					sum[i][j].x=sum[i][j-1].x+1;
					sum[i][j].y=sum[i-1][j].y+1;
				}
			}
		}

		for(int i=1;i<=n;i++){
			for(int j=i;j<=n;j++){
				for(int k=1;k<=m;k++){
					int h,w;
					h=j-i+1;
					w=min(sum[i][k].x,sum[j][k].x);
					if(sum[j][k].y>=h)for(int l=w;l>=1;l--){
						if(sum[j][k-l+1].y>=h){
							int area=l*h;
							ans=max(area,ans);
							break;
						}
					}
				}
			}
		}
		printf("%d\n",ans);
}	
	return 0;
}

J: 数列分段

时间:3 s

题目描述: 有n个整数,分成m段。使每一段的和的最小值尽可能的大。

输入

​ 第一行是一个整数T,代表测试数据的组数。每组样例中,第一行有两个整数n,m,接下来一行是n个整数。其中T<=50,n,m<1e5 。

输出

​ 尽可能的大的每一段的和的最小值。

样例输入
1
5 3
4 5 4 5 1
样例输出
4
题解

最小化最大值,二分+贪心。

我们check时对于,sum如果大于等于k立即划分为一段,num++,如果num大于等于m的,说明该k值一定满足。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[100005];
int t,r,l,ans;
bool check(int k){
    int sum=0,num=0;
    for(int i=1;i<=n;i++){
    	if(a[i]+sum>=k){
    		num++;
    		sum=0;
		}else{
			sum+=a[i];
			
		}
	}
	if(num>=m)return true;
	else return false;
}
int main(){
	scanf("%d",&t);
	while(t--){
	r=0,l=0x3f3f3f3f;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		r+=a[i];
        l=min(l,a[i]);
	}	 
	while(l<=r){
		int mid=(l+r)/2;
		if(check(mid))ans=mid,l=mid+1;
		else r=mid-1;
	}
	printf("%d\n",ans);
	}
	return 0;
}

K: 数数字

时间:3 s

题目描述:

​ Alice和Bob进行一款质数游戏,Alice先说一个质数或者1,Bob再加上一个质数或者1。不能超过目标数字,谁先喊道目标数字就获胜。Alice和Bob都足够聪明,都会采用最优策略。

输入

第一行是一个整数T,代表测试数据的组数。每组样例中,第一行有一个整数n,代表目标数字。

T<=20,n<1e18

输出

共T行,输出Alice win或者Bob win。

样例输入
2
4
6
样例输出
Bob win
Alice win
题解

首先,我们需要知道一个定理:一切大于2的质数,一定是形如4n+1或4n-1的数。

由于我们每次给的数可以是1,2,3+(大于2的)质数。那么无论谁先讲,后手的一定可以加成4n。所以说,只要给的数能够被4整除,Bob必胜。反之,Alice必胜。当然你可以找规律,也能写出来。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int t;
ll n; 
int main(){
	scanf("%d",&t);
	while(t--){
		cin>>n;
		if(n%4==0){
			printf("Bob win\n");
		}
		else 
		printf("Alice win\n");
	}
	return 0;
}
发布了2 篇原创文章 · 获赞 3 · 访问量 466

猜你喜欢

转载自blog.csdn.net/weixin_43773540/article/details/104943915