CSP-J2022 题解
一、乘方
我们看数据范围:
对于
的数据,保证
可以轻易得知,即使没有别的限制,至少也应该用快速幂解决
而这题只有一个限制:如果答案大于
用眼一看,这很简单啊,只要在快速幂计算答案时每轮判定一下答案是否大于
或者不放心的话还可以判断
但本题有坑:如果开始
但由于
因此还要在开头判定一下
#include<bits/stdc++.h> #define int long long #define endl "\n" using namespace std; const int maxed=1e9; inline int qpow(int u,int v)//快速幂板子 { int res=1; while(v) { if(v&1) res*=u; v>>=1,u*=u; if(res>maxed||res<=0) return -1;//判断答案是否溢出或是否大于 1e9 //这里其实应该也要判断一下 u 的范围 } return res; } signed main() { ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); freopen("pow.in","r",stdin); freopen("pow.out","w",stdout); //文件操作提交到洛谷的时候记得删掉 int a,b; cin>>a>>b; if(a>maxed&&b)//判断自身是否要输出 -1 //由于本题数据范围中说 a,b 均大于 1,所以 b 的判定条件也可以不写 { cout<<-1; return 0; } cout<<qpow(a,b);//输出快速幂答案 return 0; }
另外,由于今年数据 和去年一样 很水,貌似 像我一样 写得不太紧密也可以过?
二、解密
本题是一道数论题,但笔者好菜,硬把它做成了二分(捂脸)
先来说正解
我们看题目,因为本题要求
而
而
又因为
继续下去,得出
最后得到
转化为一元二次方程后,我们考虑配方(令
两边同时乘
接下来按照课本经验,我们凑数得出
套用完全平方公式
因为运算过程中
所以把
同理,因为
(之所以不把
接下来就是代码了:
#include<bits/stdc++.h> #define endl "\n" #define int long long using namespace std; int n,d,e,m,p,q; signed main() { ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); freopen("decode.in","r",stdin); freopen("decode.out","w",stdout); int T; cin>>T; while(T--) { cin>>n>>d>>e; m=(n-e*d+2)*(n-e*d+2)-(n<<2); p=((n-e*d-(int)(round(sqrt(m)))+2)>>1); q=((n-e*d+(int)(round(sqrt(m)))+2)>>1);//照上面的数学公式直接莽 //要注意的是,这里为了防止误差,从直接写 sqrt(m) 变成了 (int)(round(sqrt(m))),后面再加判断即可 if(p>0&&q>0&&n==p*q&&e*d==(p-1)*(q-1)+1) cout<<p<<" "<<q<<endl;//如果符合题目条件就输出 p,q else cout<<"NO"<<endl;//不然就输出 NO } return 0; }
另外我的写法是 二分,数学含量不高,但可能要废一些时间来调试二分边界
具体的,二分
如果
最后再判断一下,如果可行,那
#include<bits/stdc++.h> #define endl "\n" #define int long long using namespace std; int n,d,e,m; signed main() { ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); freopen("decode.in","r",stdin); freopen("decode.out","w",stdout); int T; cin>>T; while(T--) { cin>>n>>d>>e; m=n-e*d+2;//算出 m int l=1,r=m>>1; while(l<r)//注意二分边界 { int mid=l+((r-l)>>1);//二分 if(mid*(m-mid)<n) l=mid+1; else r=mid; } if(l*(m-l)!=n) cout<<"NO"<<endl;//判断,如果不可行就输出 NO else cout<<l<<" "<<m-l<<endl;//如果可行就按题解中说的输出 l 和 m-l } return 0; }
三、逻辑表达式
如果我没记错的话之前
一般来说,处理中缀表达式所用的数据结构要么是栈,要么是树,两者本质上是一样的
但树的话比较麻烦,本题仅仅用栈就可以解决
具体的,对于读入的字符串,我们先在它的两头加一个括号
对于字符串的每个字符:
如果是数字
如果是左括号 (,&,),|
)往里面压入
如果是或运算符 |
,处理就比较复杂了
首先,我们要把之前从符栈栈顶到自顶到底的第一个
接着我们往符栈栈顶压入
最后,我们考虑短路的问题。因为
当然,如果不短路就啥都不用做,短路时不要忘记把或运算符的短路计数器加
如果是与运算符
再考虑短路的情况,与或运算符不同,与运算符只需要“跳跃”一次即可
由于篇幅所限
笔者摆烂力,读者可以自行思考为什么,或找出一组说明
同样的,与运算符的短路计数器不要忘记加
最后,如果是右括号
最后,值栈的栈顶就是表达式的值,短路次数的话短路计数器已经统计过了
放上来之不易的代码:
#include<bits/stdc++.h> // #define endl "\n" using namespace std; string str; stack<bool>st_val; stack<int>st_opt; pair<int,int>shor; inline void work()//计算值栈栈顶和栈顶下面两个值 { int opt=st_opt.top(); st_opt.pop(); bool x,y; x=st_val.top(),st_val.pop(); y=st_val.top(),st_val.pop(); if(opt==1) st_val.push(x|y); else if(opt==2) st_val.push(x&y); return; } inline int nex(int np)//用来“跳跃”的函数 { if(str[np]==48||str[np]==49) return np; if(str[np]=='(') { int top=0; for(;np<str.size();np++) { if(str[np]=='(') top++; else if(str[np]==')') { top--; if(!top) return np; } } } return 1919810;//Homo 特有的返回值(喜) } signed main() { ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); freopen("expr.in","r",stdin); freopen("expr.out","w",stdout); cin>>str,str="("+str+")";//先把表达式左右两边套上一层括号 for(int i=0;i<str.size();i++) { if(str[i]==48) st_val.push(0); else if(str[i]==49) st_val.push(1);//如果是数字直接往值栈里面压入 else if(str[i]=='(') st_opt.push(-1);//如果是左括号往符栈里面压入即可 else if(str[i]=='|')//如果是 | { while(!st_opt.empty()&&st_opt.top()>0) work();//先清空一段括号序列中表达式的值 st_opt.push(1);//压入标记 if(st_val.top())//满足短路条件 { shor.second++;//或运算符短路计数器加 1 int point=i; while(114514)//Homo 特有的判定条件(喜) { point=nex(point+1); if(str[point+1]==')'||str[point+1]=='|') break; else point++; } i=point,st_opt.pop();//跳跃,删除这个或符号的标记(已经运算过了) } } else if(str[i]=='&')//如果是 & { while(!st_opt.empty()&&st_opt.top()>1) work();//先清空一段括号序列 st_opt.push(2);//压入与运算符的标记 if(!st_val.top()) shor.first++,i=nex(i+1),st_opt.pop();//同或运算符,但这次只需要“跳跃” 1 次 } else if(str[i]==')')//如果是 ) { while(!st_opt.empty()&&st_opt.top()>-1) work();//先清空该括号所包含的括号序列 st_opt.pop();//再删去一个 } } cout<<st_val.top()<<endl<<shor.first<<" "<<shor.second;//输出答案 return 0; }
四、上升点列
我们看这道题
第一种解法:动态规划
我们先以横坐标为第一关键字,纵坐标为第二关键字对整个点的序列排序
排序后,用
我们考虑用三重循环:第一重循环枚举
具体的,对于每个
然后看
具体细节看代码吧,这里说不明白
#include<bits/stdc++.h> #define endl "\n" using namespace std; int n,m,ans; int f[509][109]; pair<int,int>a[509]; signed main() { ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); freopen("point.in","r",stdin); freopen("point.out","w",stdout); cin>>n>>m;//因为动态规划循环中要用 k,所以把输入中的 k 换成 m for(int i=1;i<=n;i++) cin>>a[i].first>>a[i].second; sort(a+1,a+n+1);//pair 默认以 first 为第一关键字,second 为第二关键字排序 for(int i=1;i<=n;i++) for(int j=0;j<=m;j++) { f[i][j]=j+1;//先赋值初始状态(最小值) for(int k=1;k<i;k++) { int dis=a[i].first-a[k].first+a[i].second-a[k].second;//算出它们距离 if(a[k].second>a[i].second||!dis||j+1<dis) continue; //1. 方向错了(a[k].second>a[i].second) : continue //2. 两点重合(!dis) : continue //3. 不够搭上(j+1<dis) : continue f[i][j]=max(f[i][j],f[k][j-dis+1]+dis);//更新 //枚举 k(中转点):1~k 用 j-dis+1 个自由点,(k+1)~i 用 dis 个 } ans=max(ans,f[i][j]+m-j);//更新答案 } cout<<ans; return 0; }
第二种解法:最短路
和动态规划思想差不多,也是根据
只不过需要先建边
(事实上
#include<bits/stdc++.h> #define endl "\n" #define int long long using namespace std; int n,k,a[509][2]; int gra[509][509],res[509][509]; int ans; signed main() { ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); //freopen("point.in","r",stdin); //freopen("point.out","w",stdout); memset(res,0x3f,sizeof(res)); cin>>n>>k; for(int i=1;i<=n;i++) cin>>a[i][0]>>a[i][1];//Floyd 不需要排序 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(a[j][0]>=a[i][0]&&a[j][1]>=a[i][1]) res[i][j]=gra[i][j]=a[j][0]-a[i][0]+a[j][1]-a[i][1]-1;//建边,去掉首尾 for(int k=1;k<=n;k++)//枚举中转点(作用在解法一中说了) for(int i=1;i<=n;i++) { if(i==k) continue; for(int j=1;j<=n;j++) { if(i==j||j==k) continue; res[i][j]=min(res[i][j],res[i][k]+res[k][j]);//Floyd 松弛方程 } } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(res[i][j]<=k) ans=max(ans,gra[i][j]+k-res[i][j]);//和解法一一样统计答案 cout<<(ans+=2);//最后再补上首尾两端的两个点 return 0; }
第三种解法:记忆化搜索
把第一种解法动态规划稍微改变一下就可以变成记忆化搜索,这里不再过多阐述
代码:
#include<bits/stdc++.h> #define endl "\n" using namespace std; int n,m,ans; int a[1009][2]; int f[1009][1009]; inline int dfs(int p,int s) { if(~f[p][s]) return f[p][s]; int res=s; for(int i=1;i<=n;i++) { if(a[i][0]<a[p][0]||a[i][1]<a[p][1]) continue; int dis=a[i][0]-a[p][0]+a[i][1]-a[p][1]; if(!dis||s+1<dis) continue; res=max(res,dfs(i,s-dis+1)+dis); } return f[p][s]=res; } signed main() { ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); freopen("point.in","r",stdin); freopen("point.out","w",stdout); memset(f,-1,sizeof(f)); cin>>n>>m; for(int i=1;i<=n;i++) cin>>a[i][0]>>a[i][1]; for(int i=1;i<=n;i++) ans=max(ans,dfs(i,m)); cout<<++ans; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效