A.星期几考试
题意:有t组样例,给出三个正整数x,y,z,其中z表示y天前是星期z,求x天后是星期几.
思路分析:签到题,易知本题的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”)
思路分析:签到水题,定义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)的个位数.
思路分析:超级无敌签到大水题,首先观察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.否则输出操作次数。
思路分析:对于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的字段长度
思路分析:一看到最长连续子区间以及其值固定为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必须同时出现才算有效子序列.
思路分析: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”。
思路分析:骰子在每一格都有不同的走法,也有不同的到达方式,容易想到,这一定是一道多状态的动态规划(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]).
当不使用骰子时,就是上述转移方程式的前一个选项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.".
思路分析:对于每次进攻方向一定是目前所在位置的四周,类似于按层次遍历,则该题的搜索方式一定是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;
}