上海大学第21届ACM程序设计联赛(春季赛)部分题解

文章目录

  • 前言
  • [A.Antiamuny wants to learn binary search](#A.Antiamuny wants to learn binary search)
  • [B.Bespread with chequers](#B.Bespread with chequers)
  • [G.Golden jade matrix checker](#G.Golden jade matrix checker)
  • [H.How to know the function](#H.How to know the function)
  • [I.I like UNO !](#I.I like UNO !)
  • [J.Juxtaposed brackets](#J.Juxtaposed brackets)
  • 后记

前言

出于某些原因,模拟了一下2023年上海大学ACM校赛练练手,随便写了几个题,整理一下自己的题解。
题目地址

傻呗签到题,略。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int f(int l,int r,int x) { // l <= x <= r
	int cnt = 0;
	while(l <= r) {
		cnt++;
		int mid = (l + r) / 2;
		if (mid == x) 
			break;
		if (mid < x) 
			l = mid + 1;
		else 
			r = mid - 1;
	}
	return cnt;
}

int main(){
	int t;
	cin >> t;
	while(t--){
		int l,r,x;
		cin >> l >> r >> x;
		cout << f(l,r,x) << endl;
	}
} 

B.Bespread with chequers

稍加观察可以得到,对于 2 × n 2×n 2×n的情况下,可以由 2 × ( n − 1 ) 2×(n-1) 2×(n−1)的基础上拼一个 2 × 1 2×1 2×1的块,或者在 2 × ( n − 2 ) 2×(n-2) 2×(n−2)的基础上拼一个 2 × 2 2×2 2×2或两个 2 × 1 2×1 2×1的块,即 a n = a n − 1 + 2 a n − 2 a_{n}=a_{n-1}+2a_{n-2} an=an−1+2an−2

由于题目数据范围在 1 e 6 1e6 1e6内,但考虑到有 1 e 3 1e3 1e3组数据,考虑打表 ,时间复杂度 O ( n ) O(n) O(n)。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll maxn=1e6;
const ll mod=1e9+7;
int a[maxn+10];
int main(){
    int t;
    cin >> t;
    a[1]=1,a[2]=3;
    for(int i=3;i<=maxn;i++){
        a[i]=(2*a[i-2]%mod+a[i-1])%mod;
    }
    while(t--){
        int n;
        cin >> n;
        cout << a[n] << endl;
    }
}

G.Golden jade matrix checker

按照定义就是,给定一个 n × m n \times m n×m矩阵,要求内部所有数字之和为正数,任意一个 h × w h\times w h×w的子矩阵数字之和为负数。

考虑维护二维前缀和 ,令 p r e f i x [ i ] [ j ] prefix[i][j] prefix[i][j]表示 [ 1 ∼ i ] , [ 1 ∼ j ] [1\sim i],[1\sim j] [1∼i],[1∼j]的元素之和

,那么任意一个左上角为 [ i , j ] [i,j] [i,j]子矩阵的表示方法为
p r e f i x [ i + h − 1 ] [ j + w − 1 ] − p r e f i x [ i − 1 ] [ j + w − 1 ] − p r e f i x [ i + h − 1 ] [ j − 1 ] + p r e f i x [ i − 1 ] [ j − 1 ] prefix[i+h-1][j+w-1]-prefix[i-1][j+w-1]-prefix[i+h-1][j-1]+prefix[i-1][j-1] prefix[i+h−1][j+w−1]−prefix[i−1][j+w−1]−prefix[i+h−1][j−1]+prefix[i−1][j−1]

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll maxn=2e3;
const ll mod=1e9+7;
ll a[maxn+10][maxn+10];
ll prefix[maxn+10][maxn+10];//prefix[i][j]表示1~i/1~j之和 
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n,m,h,w;
		scanf("%d%d%d%d",&n,&m,&h,&w);
		ll sum=0;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;++j){
				cin >> a[i][j];
				prefix[i][j]=0;
				sum+=a[i][j];
			}
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				prefix[i][j]= prefix[i][j-1]+a[i][j];
			}
		}
		for(int j=1;j<=m;++j){
			for(int i=1;i<=n;++i){
				prefix[i][j]+=prefix[i-1][j];
			}
		}
		if(sum<=0){
			printf("NO\n");
			continue;
		}
		bool flag=true;
		for(int i=1;i+h-1<=n && flag;++i){
			for(int j=1;j+w-1<=m && flag;++j){//[i,j]~[i+h-1,j+w-1]
				ll sum=prefix[i+h-1][j+w-1]-prefix[i-1][j+w-1]-prefix[i+h-1][j-1]+prefix[i-1][j-1];
				if(sum>=0){
					flag=false;
					break;
				}
			}
		}
		printf("%s\n",flag?"YES":"NO");
	}
} 

H.How to know the function

傻呗签到题,只需注意到 n = 0 n=0 n=0时只需询问一次即可。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll maxn=2e3;
const ll mod=1e9+7;
ll a[maxn+10][maxn+10]; 
int main(){
	int t;
	cin >> t;
	while(t--){
		ll n;
		cin >> n;
		if(n==0){
			cout << "1" << endl;
		}else {
			cout << "2" << endl;
		}
	}
} 

I.I like UNO !

大模拟题,规则于普通UNO有所简化

需要注意几个点:

  1. 需要用 O ( 1 ) O(1) O(1)查询的结构去维护每个玩家的牌,我采用了二维数组计数的方式,将四个花色与13类牌按照优先级划分,构造出 4 × 13 4\times13 4×13的矩阵来维护。
  2. 牌库需要一直维护,玩家打过的牌会回收到牌库底,在原本的牌库被抽完一轮之后之前打过的牌就成为了牌库顶。我当时采用的数组双指针的方式去处理,但在最后一刻计算错了数组的大小导致最后两组数据一直不对卡了半个多小时。
  3. 当玩家有牌可出时,优先级是 "同花色但优先级更高的牌">"符号相同但颜色更高级的牌">"同花色但优先级更低的牌"
    举个例子,如果上家出的是蓝+2,那蓝0~蓝9>红+2>黄+2>蓝+2>绿+2>蓝反转>蓝跳过
  4. 当玩家无牌可出需要抽牌时,如果可以打出就必须立刻打出,如果是效果牌必须立刻结算
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll maxn=3e6;
const ll mod=1e9+7;
const int total=4;
struct Card{
	int type;//花色 
	int number;//标识 
};
int priorType(char c){//花色优先级 (数值越小越优先)
	if(c=='R'){
		return 1;
	}
	if(c=='Y'){
		return 2;
	}
	if(c=='B'){
		return 3;
	}
	if(c=='G'){
		return 4;
	}
	return 4;
}
int priorNumber(char c){//数字优先级 
	if(c>='0' && c<='9'){
		return 1+(c-'0');
	}
	if(c=='+'){
		return 11;
	}
	if(c=='R'){
		return 12;
	}
	if(c=='S'){
		return 13; 
	}
	return 13; 
}
int player[total+10][10][20];//玩家-花色-优先级 
Card deck[maxn+10];//牌堆
int handSize[total+10];//手牌数
int isWinning(){
	for(int i=1;i<=total;i++){
		if(handSize[i]==0){
			return i;
		}
	}
	return -1;
}
int getNextPlayer(int current,int sgn){//确定出牌下家 
	int nextPlayer=current;
	if(sgn==1){//正向
		nextPlayer++;
		if(nextPlayer>total){
			nextPlayer-=total;
		}
	}else{
		nextPlayer--;
		if(nextPlayer<1){
			nextPlayer+=total;
		}
	}
	return nextPlayer;
}
int main(){
	for(int i=1;i<=total;++i){
		for(int j=1;j<=5;j++){
			char temp[10];
			scanf("%s",temp);
			player[i][priorType(temp[0])][priorNumber(temp[1])]++;
		}
		handSize[i]=5;
	}
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		char temp[10];
		scanf("%s",temp);
		deck[i].type=priorType(temp[0]);
		deck[i].number=priorNumber(temp[1]);
	}
	int current=1;//当前出牌玩家
	int sgn=1;//下一位玩家的顺序(默认为正) 
	int deckTop=1;//当前牌堆顶 
	int deckBottom=n;//当前牌堆底 
	int lastType=deck[n].type;
	int lastNumber=deck[n].number;
	while(isWinning()==-1){
		bool flag=false;//是否出过牌 
		for(int i=1;i<=lastNumber-1 && !flag;i++){//相同花色数字牌
			if(player[current][lastType][i]>0){
					player[current][lastType][i]--;
					lastNumber=i;//更新前一张牌数字 
					handSize[current]--;
					flag=true;
					break;
			}
		}
		for(int i=1;i<=4 && !flag;i++){//相同数字的异色牌
			if(player[current][i][lastNumber]>0){
				player[current][i][lastNumber]--;
				lastType=i;//更新前一张牌花色 
				handSize[current]--;
				flag=true;
				break;
			}
		}
		for(int i=lastNumber+1;i<=13 && !flag;i++){//相同花色功能牌
			if(player[current][lastType][i]>0){
				player[current][lastType][i]--;
				lastNumber=i;//更新前一张牌数字
				handSize[current]--; 
				flag=true;
				break;
			}
		}
		if(flag){//如果打出了一张牌
			deckBottom++;
			deck[deckBottom].type=lastType;
			deck[deckBottom].number=lastNumber;//更新底部
			if(lastNumber>=1 && lastNumber<=10){//数字牌正常更新下家 
				current=getNextPlayer(current,sgn);
			} 
			else if(lastNumber==11){//+2牌,让下家抽2,并跳过 
				int nextPlayer=getNextPlayer(current,sgn);//确定出牌下家
				player[nextPlayer][deck[deckTop].type][deck[deckTop].number]++;
				player[nextPlayer][deck[deckTop+1].type][deck[deckTop+1].number]++;
				handSize[nextPlayer]+=2;//手牌数+2
				deckTop+=2;//抽2张牌
				current=getNextPlayer(nextPlayer,sgn);//下家被跳过 
			}
			else if(lastNumber==12){//反转牌 
				sgn=-sgn;
				current=getNextPlayer(current,sgn);//反向顺序 
			}
			else if(lastNumber==13){//跳过牌 
				int nextPlayer=getNextPlayer(current,sgn);
				current=getNextPlayer(nextPlayer,sgn);//下家被跳过 
			}
		}
		else{//没出牌,摸牌
			if(lastType==deck[deckTop].type || lastNumber==deck[deckTop].number){//抽到的牌可以立刻被打出 
				lastType=deck[deckTop].type;
				lastNumber=deck[deckTop].number;
				deckBottom++;
				deck[deckBottom].type=lastType;
				deck[deckBottom].number=lastNumber;//更新底部
				deckTop++;
				if(lastNumber>=1 && lastNumber<=10){//数字牌正常更新下家 
					current=getNextPlayer(current,sgn);
				}
				else if(lastNumber==11){//+2牌,让下家抽2,并跳过 
					int nextPlayer=getNextPlayer(current,sgn);//确定出牌下家 
					player[nextPlayer][deck[deckTop].type][deck[deckTop].number]++;
					player[nextPlayer][deck[deckTop+1].type][deck[deckTop+1].number]++;
					handSize[nextPlayer]+=2;//手牌数+2
					deckTop+=2;//抽2张牌
					current=getNextPlayer(nextPlayer,sgn);//下家被跳过 
				}
				else if(lastNumber==12){//反转牌 
					sgn=-sgn;
					current=getNextPlayer(current,sgn);//反向顺序 
				}
				else if(lastNumber==13){//跳过牌 
					int nextPlayer=getNextPlayer(current,sgn);
					current=getNextPlayer(nextPlayer,sgn);//下家被跳过 
				}
			}
			else{//还是没能打出牌 
				player[current][deck[deckTop].type][deck[deckTop].number]++;
				handSize[current]++;//手牌数+1
				deckTop++;
				current=getNextPlayer(current,sgn);//正常轮换下家 
			}
		}
	}
	printf("%c\n",'A'+isWinning()-1);
} 

J.Juxtaposed brackets

真没想到编译原理也可以出题啊,你怎么知道我编译原理LL(1)文法学的一坨

卡了半天没想通应该怎么写,题解也有点似懂非懂,先欠着慢慢思索吧。

后记

一共四小时,前一小时写了4个题,以为手感火热,结果J卡了好会儿,最后时间都顾着写大模拟题去了,有点幽默。

感觉自己突然年轻了几岁,好像回到过去熬夜打codeforces的时间了。

写完这篇博客就去好好复习编译原理。
DrGilbert 2024.3.8

相关推荐
XiaoLeisj6 分钟前
【递归,搜索与回溯算法 & 综合练习】深入理解暴搜决策树:递归,搜索与回溯算法综合小专题(二)
数据结构·算法·leetcode·决策树·深度优先·剪枝
yuyanjingtao20 分钟前
CCF-GESP 等级考试 2023年9月认证C++四级真题解析
c++·青少年编程·gesp·csp-j/s·编程等级考试
Jasmine_llq25 分钟前
《 火星人 》
算法·青少年编程·c#
闻缺陷则喜何志丹36 分钟前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
charlie1145141911 小时前
C++ STL CookBook
开发语言·c++·stl·c++20
Lenyiin1 小时前
01.02、判定是否互为字符重排
算法·leetcode
小林熬夜学编程1 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
倔强的石头1061 小时前
【C++指南】类和对象(九):内部类
开发语言·c++
鸽鸽程序猿1 小时前
【算法】【优选算法】宽搜(BFS)中队列的使用
算法·宽度优先·队列