“2023年广东工业大学腾讯杯新生程序设计竞赛”题解solution(A-E,G,I,J)


A.星期几考试
题意:有t组样例,给出三个正整数x,y,z,其中z表示y天前是星期z,求x天后是星期几.

14e2a3593ec740ba944850c24bb04cda.png

思路分析:签到题,易知本题的x,y,z天数的相加一定会与模7有关,设x天后为s,则s=(x+y+z)%7,然而当(x+y+z)的和为7的倍数时,结果将会输出0;一个妥善的处理方法是对7的倍数进行特判,笔者却想到了高中学习vb中的“约瑟夫环”的相应技巧,即s=(x+y+z-1)%7+1,本题结。

Code:

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
int T,x,y,z,res;
signed main(){
    std::ios::sync_with_stdio(false);
    cin>>T;
    while(T--){
        cin>>x>>y>>z;
        z=z%7;
        y=(y+z-1)%7+1;
        res=(x+y-1)%7+1;
        cout<<res<<endl;
    }
    return 0;
}

B.hb的训练赛
题意:给出两个数字n和m(n,m<=1e5),表示训练赛有n道题和m次提交;输入m次字符串,当出现“AC”之前,题目的任何错误提交都算做一次罚时,“AC”之后则不算,如若该题最后没有“AC”,则不算罚时。求比赛过程中的通过题目数和有效罚时数量。(PS:哪家正规比赛的评测机会因为换行和格式报“PE”)

7e0f8f43f28f442699a1aa8b39c5289b.png

思路分析:签到水题,定义bool类数组flag用于记录当前该题是否“AC”,再定义一个f数组记录当前罚时数量;如果flag[i]=0且输入为"AC",则flag[i]=1,否则f[i]++;最后对n道题进行遍历统计,若flag[i]=1,则证明该题已通过,总题数加一并累加f[i]到总罚时。

Code:

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
int n,m,sum,cnt,ans,f[100010];
bool flag[100010];
string s; 
signed main(){
    std::ios::sync_with_stdio(false);
    cin>>n>>m;
    while(m--){
    	cin>>cnt>>s;
    	if(s=="AC"&&!flag[cnt])flag[cnt]=1;
    	else if(!flag[cnt])f[cnt]++;
	}
	for(int i=1;i<=n;i++)
		if(flag[i])ans++,sum+=f[i];
	cout<<ans<<" "<<sum<<endl;
	return 0;
}

C.信件
题意:有t组样例,每组样例给出一个正整数n(1<=n<=1e18),求(2^1+1)(3^1)(2^2+1)(3^2)...(2^n+1)(3^n)的个位数.

67ae4878d24f4e788d2c68975165c1bf.png

思路分析:超级无敌签到大水题,首先观察n的范围,1<=n<=1e18,极大的复杂度,暴力法取高精的时间复杂度连O(N)也办不到,所以只能使用二分法( O(logN) )或者奇技淫巧的规律解题。找规律可知,当n=1时,原式=3*3=9;当n=2时,因式中2^2+1=5,而其余每一项形如2^1+1与3^n皆为奇数,奇数的乘积一定为奇数,而末尾为5的数乘以奇数,其末尾一定也为5;所以只有n=1时,结果为9,其余结果皆为5.

Code:

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
int T,n;
signed main(){
    std::ios::sync_with_stdio(false);
    cin>>T;
    while(T--){
    	cin>>n;
    	if(n==1)cout<<9<<endl;
    	else cout<<5<<endl;
	}
	return 0;
}

D.乘除法
题意:有t组询问,每一组询问时会输入x和y(x,y<=1e9),x可通过乘以5或者当其为6的倍数时除以6,最终能否变换为y,如果不能,则输出-1.否则输出操作次数。

a45eb32ac4014345bab97d3452cd7953.png

思路分析:对于x和y的转化条件,设乘以5的操作次数为i,除以6的操作次数为j,则x*(5^i)/(6^j)=y,对于整数的除法,最好解决途径是转化为乘积的形式,易得x*(5^i)=y*(6^j),其中i,j>=1;也就是通过对i和j的操作次数进行枚举,判断条件是等式两边相等,进而解得相应的值。
对于i和j的范围取值,估算得出5和6的十次方左右便逼近1e9,而x/y的取值恰恰也在[1,1e9],两者乘积最高高达1e18,无限接近long long的极限,所以对于i和j需要控制好精度或者将数据类型定义成__int128(可达1e35左右)。本题笔者对i和j取值12即可AC。注意,对于5^i和6^j请不要使用pow函数!!!因为pow函数的数据类型是浮点数,很有可能因为精度而产生错误(虽然经过亲测pow函数是可以过的),最好手写一个幂函数的方法。

Code:

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
int t,x,y;
int mul(int n,int k){
    int ans=1;
    for(int i=1;i<=k;i++)
        ans*=n;
    return ans;
}
signed main(){
    std::ios::sync_with_stdio(false);
    cin>>t;
    while(t--){
    	cin>>x>>y;
    	bool flag=0;
    	int ans=1e9;
    	for(int i=0;i<=12;i++)
    		for(int j=0;j<=12;j++)
    			if(x*mul(5,j)==y*mul(6,i))
                    flag=1;
    	if(!flag)cout<<"-1"<<endl;
    	else{
    		for(int i=0;i<=12;i++)
    			for(int j=0;j<=12;j++)
    				if(x*mul(5,j)==y*mul(6,i))
    					ans=min(i+j,ans);
    		cout<<ans<<endl;
		}				
	}
	return 0;
}

E.不知道叫什么名字
题意:给出一个正整数n以及对应的数组a1,a2...an,其中ai的取值只能是1或者0.求最长的一段具有相同数量的0和1的字段长度

7bcdf68584ee441a9bfc3ec538211d24.png

思路分析:一看到最长连续子区间以及其值固定为0和1,笔者立刻联想到Leetcode的经典原题:求数组中固定和为k的最长字段——

只需要遍历统计前缀和,当搜到第i个数得到pre[i]时,要想找到最长的和为k的子段长度,则需要定位到第一次前缀值出现pre[i]-k的下标(因为pre[i]-(pre[i]-k)=k),所以需要事先开一个map<int,int>mp,键表示前缀值,值表示下标,遍历的时候若不存在mp[pre[i]]则进行初始化,当不存在mp[pre[i]-k]则跳过,否则对结果进行取最大值(max)操作进行维护.


——回到本题,由于0和1的子段的和并不是固定值,所以我们想办法转化成固定值即可。而最优的处理方式就是把每一个0改为-1,则相同数量的1和-1字段和就变为了0!所以上面题目中的k取0,说明我们只需要统计前缀和相同的最大下标差即可使用O( n )的复杂度AC本题.

Code:

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
int n,a[100010],pre[100010];
map<int,int>mp;
signed main(){
    std::ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;i++){
    	cin>>a[i];
    	if(a[i]==0)a[i]=-1;
	}
	int maxlen=0;
    mp[0]=0;
	for(int i=1;i<=n;i++){
		pre[i]=pre[i-1]+a[i];
		if(mp.count(pre[i]))maxlen=max(i-mp[pre[i]],maxlen);
		else mp[pre[i]]=i;
	}
	cout<<maxlen<<endl;
	return 0;
}

G.闪闪发光心动不已!

题意:给出一段小写字符串,找出相对顺序排列成kirakirakira...dokidokidoki...的子序列的最大长度,只需统计kira和doki的相应数量,其他多余部分不考虑,其中kira和doki必须同时出现才算有效子序列.

18eb749de4b143c89791e5b37f7af273.png

思路分析:kira和doki一定是符合相应顺序的,我们定义sumk和sumd用于统计序列的个数,并且有先后关系,只有sumk或sumd等于4时才能算完整搜到一次单词,再对变量进行清零。同时,必须是kira在前方扎堆出现,doki在后方出现,所以我们定义两个数组prek[i]和sufd[i]用于统计前i个字符的“kori”的单词总数和统计后i个字符的“doki”的单词总数,前者就是单纯的前缀和,当sumk=4时对prek[i]+=4即可,而后者注意搜索顺序是“ikod”。最后对每一位位置进行枚举,当prek[i]>0且sufd[i]>0(说明已存在这两个单词),对结果进行取最大值(max)操作进行维护.

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
//kira doki
int n,sumk,sumd,prek[100010],sufd[100010];
char s[100010];
signed main(){
    std::ios::sync_with_stdio(false);
    cin>>n>>s+1;
    for(int i=1;i<=n;i++){
    	prek[i]=prek[i-1];
    	if(s[i]=='k'&&sumk==0)sumk++;
    	else if(s[i]=='i'&&sumk==1)sumk++;
    	else if(s[i]=='r'&&sumk==2)sumk++;
    	else if(s[i]=='a'&&sumk==3)sumk++;
    	if(sumk==4)prek[i]+=4,sumk=0;
	}
    for(int i=n;i>=1;i--){
    	sufd[i]=sufd[i+1];
    	if(s[i]=='i'&&sumd==0)sumd++;
    	else if(s[i]=='k'&&sumd==1)sumd++;
    	else if(s[i]=='o'&&sumd==2)sumd++;
    	else if(s[i]=='d'&&sumd==3)sumd++;
    	if(sumd==4)sufd[i]+=4,sumd=0;
	}
	int ans=0;
	for(int i=1;i<=n;i++)
		if(prek[i]&&sufd[i+1])ans=max(prek[i]+sufd[i+1],ans);
	cout<<ans<<endl;
	return 0;
}

I.uu爱玩飞行棋
题意:输入n和m(1<=n,m<=1000),分别代表起点与终点的距离和拥有的定向骰子的数量;再给出m个数组a,ai代表第i个定向骰子的点数。使用第i个骰子,飞机会前进ai格,若恰好到达终点,则该次即可通关;而若超出终点,则会反弹超出部分的点数的距离。 输出通关最少需要的骰子数量,如果没法获得胜利,则输出“-1”。

a15f33bac4ef4e069459ace30fe5d597.png

思路分析:骰子在每一格都有不同的走法,也有不同的到达方式,容易想到,这一定是一道多状态的动态规划(dp)题目。设dp[i]表示到达第i格所需的最小骰子数量,接下来我们来思考它的状态转移方程——
当我们使用第i个骰子时,
Ⅰ.从前方直接到达该格子,若当前位置为j,则前方位置应为j-a[i],故dp[j]=min(dp[j],dp[j-a[i]]+1);
Ⅱ.反弹到达该格子,若当前位置为j,则起始位置y满足方程式(n-y)+(n-j)=a[i],即y=2*n-a[i]-j,故dp[j]=min(dp[j],dp[2*n-j-a[i]).

eaaa6f04fef64ed4a1429d327989fe74.png
当不使用骰子时,就是上述转移方程式的前一个选项dp[j].
因为这道题的范围是交叉的,难以在一个if-else判断语句内完成所有情况的转移,所以我们分开来进行书写.

int x=j-a[i];
dp[j]=min(dp[j],dp[x]+1);
int y=2*n-a[i]-j;
if(y<=n)dp[j]=min(dp[j],dp[y]+1); 

最后在开头进行初始化操作,因为原题要求是求最小值,我们初始化为一个极大值,再对真实状态dp[0]赋值为0,防止一些无效状态进行转移进而干扰结果;最后判断dp[n]是否为初始值,若不是,说明该状态已成功被覆盖,即终点能够到达并dp[n]代表的就是最小值,也就是本题答案,反之,则没法通关,输出-1.
然而,这样交上去会得到大大的WA!笔者当时比赛就是栽倒在这个点上,仔细读题,这是一条无限长的直线,并且不能保证当n过小时,不会弹到默认起点之前!所以我们原先所设定的是0-n格,在特殊情况下可能会弹到负数格,此时数组下标越界,无法输出!为了改正此种特殊值,可采取的方式是将起点格子设为6或者更大值(因为最大点数是6,反弹到的格子值不可能低于-6),以防数组下标显负数,抑或者定义map<int,int>存负值的坐标,最后一定要记得在遍历和初始化的时候包括所有的可能取值范围!

#include<bits/stdc++.h>
#define int long long
const int INF=1e10;
using namespace std;
int n,m;
int a[1010];
map<int,int>dp;
signed main(){
    ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i=-1000;i<=1000;i++)dp[i]=INF;
    dp[0]=0;
    for(int i=1;i<=m;i++)cin>>a[i];
    for(int i=1;i<=m;i++){
        for(int j=n;j>=-1000;j--){//一定要注意倒着存值!!!因为转移只能一次性转移,否则就类似于“完全背包”模型的无限次存值
            int x=j-a[i];
            dp[j]=min(dp[j],dp[x]+1);
            int y=2*n-a[i]-j;
            if(y<=n)dp[j]=min(dp[j],dp[y]+1);  
        }
    }
    if(dp[n]!=INF)cout<<dp[n]<<endl;
    else cout<<"-1"<<endl;;
}

J.火柴人小游戏
题意:给出四个整数n,m,x,y(1<=n,m<=1000,1<=x<=n,1<=y<=m),表示n*m的矩阵大小和初始坐标(x,y),接下来n*m个数据,表示各个火柴人的初始战斗力。当初始坐标的火柴人进行战斗时,如果能击败隔壁的火柴人则其活动范围扩大,如果不能则需修改初值,同时,每打败一个火柴人即可吸收他的战斗力;问:需要修改多大的初始战斗力才能通关?如果不需要修改,则输出"No cheating need.".

0fa70d532a9f483e9107279589b04e17.png
思路分析:对于每次进攻方向一定是目前所在位置的四周,类似于按层次遍历,则该题的搜索方式一定是BFS(广度优先搜索)。对于每次的广度搜索,为了使得初始提升值最小,局部的最优解(贪心算法)一定是从最小值开始战斗和吸收,当战斗力不够则必须提高初始值以达到当前所需的战斗力,可证明,局部的最优解可推广到全局。为了维护贪心的思想,也就是需要找到每次战斗的最小值,可以将普通的队列(queue)升级为优先队列(priority_queue)的小根堆进行维护,即priority_queue<Type,vector<Type>,greater<Type>>.(升序排列)
然而想要优先队列对各个值自动排序并Type囊括题目中的信息,我们就必须定义一个pair二元结构体,并将pair的第一关键字设为战斗力(优先队列内部一定是默认对第一关键字进行排序),同时第二关键字可再度嵌套一个pair用于存坐标值以便后续的取值,故设pair<int,pair<int,int>>.
最后我们只需要统计最终的add_power,每当战斗力不足时,将他填充至所需战斗力,并将差值累加到add_power即可;若add_power=0,则说明不需要修改战斗力完成,输出"No cheating need.",反之,则输出起始值加上add_power的结果.

Code:

#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
int n,m,x,y,now_power,add_power,a[1010][1010];
int dx[]={1,0,0,-1},dy[]={0,1,-1,0};
bool vis[1010][1010]; 
typedef pair<int,pair<int,int>>pii;
priority_queue<pii,vector<pii>,greater<pii>>q;
void bfs(){
	pii t=q.top();q.pop();
	int ix=t.second.first,iy=t.second.second;
	for(int i=0;i<4;i++){
		int xx=ix+dx[i],yy=iy+dy[i];
		if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&!vis[xx][yy])
			vis[xx][yy]=1,q.push((pii){a[xx][yy],{xx,yy}});
	}
	while(!q.empty()){
		pii t=q.top();q.pop();
		ix=t.second.first,iy=t.second.second;
		if(now_power<a[ix][iy]){
			add_power+=a[ix][iy]-now_power;
			now_power=a[ix][iy];
		}
		now_power+=a[ix][iy];
		for(int i=0;i<4;i++){
			int xx=ix+dx[i],yy=iy+dy[i];
			if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&!vis[xx][yy])
				vis[xx][yy]=1,q.push((pii){a[xx][yy],{xx,yy}});
		}
	}
}
signed main(){
    std::ios::sync_with_stdio(false);
    cin>>n>>m>>x>>y;
    for(int i=1;i<=n;i++)
    	for(int j=1;j<=m;j++)
    		cin>>a[i][j];
    now_power=a[x][y],vis[x][y]=1;
    q.push((pii){a[x][y],{x,y}});bfs();
    if(add_power==0)cout<<"No cheating need."<<endl;
    else cout<<a[x][y]+add_power<<endl;
	return 0;
}

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值