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

前言

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

A.Antiamuny wants to learn binary search

傻呗签到题,略。

#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×(n1)的基础上拼一个 2 × 1 2×1 2×1的块,或者在 2 × ( n − 2 ) 2×(n-2) 2×(n2)的基础上拼一个 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=an1+2an2
由于题目数据范围在 1 e 6 1e6 1e6内,但考虑到有 1 e 3 1e3 1e3组数据,考虑打表,时间复杂度 O ( n ) O(n) O(n)

#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] [1i],[1j]的元素之和
,那么任意一个左上角为 [ 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+h1][j+w1]prefix[i1][j+w1]prefix[i+h1][j1]+prefix[i1][j1]

#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只需询问一次即可。

#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. 当玩家无牌可出需要抽牌时,如果可以打出就必须立刻打出,如果是效果牌必须立刻结算
#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

  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值