这场模拟赛的题目处了T5莫名AC以外,都不是很难.
这场考试六道题,4个小时完成.
T1:
水题一道.
题目大概就是给出T个询问,每个询问输入一个数p,问这个数是不是立方差数.
数据量是T<=100,p<=10^18.
一开始以为要卡long long,卡常数,于是常数好处理啊,long long根本没管它.
那么题目大概就是暴力枚举,但是我用二分AC了.
代码如下:
#include<bits/stdc++.h> using namespace std; #define rep(i,j,k) for (int i=j;i<=k;i++) //专属神奇循环简写 #define fuck(mid) mid*mid*mid int T; long long p=0; bool flag=0; inline void into(){ scanf("%lld",&p); flag=0; } inline void work(){ long long l=1,r=1000001,mid=(l+r)>>1; while (l+1<r){ if (fuck(mid)==p) { flag=1; return; } if (fuck(mid)>p) r=mid; else l=mid; mid=(l+r)>>1; } if (fuck(l)==p) flag=1; else if (fuck(r)==p) flag=1; } inline void outo(){ if (flag) printf("YES\n"); else printf("NO\n"); } int main(){ scanf("%d",&T); rep(i,1,T){ into(); work(); outo(); } return 0; }
第二道题也比较水.
也就是一个数论题.
这道题的大概意思就是给出T个询问,每个询问一个质数p,问p是不是平方差数.
那么分析一下性质,好像挺简单.
因为p是质数,又要等于a^3-b^3,所以把p因式分解一下就会得出p=(a-b)(a^2+ab+b^2).
因为a>=1,b>=1,所以a-b为1.
所以a=b-1
所以我们只需要枚举a或b就行了.
时间复杂度O(sqrt(p)T).
所以代码如下:
#include<bits/stdc++.h> using namespace std; #define rep(i,j,k) for (int i=j;i<=k;i++) //专属神奇循环简写 #define fuck(i) i*i*i long long p; int T; bool flag; inline void into(){ scanf("%lld",&p); } inline void work(){ flag=0; for (int i=1;i<=int(sqrt(p));i++) if (fuck((long long)(i+1))-fuck((long long)(i))==p) { flag=1; return; } } inline void outo(){ if (flag) printf("YES\n"); else printf("NO\n"); } int main(){ scanf("%d",&T); rep(i,1,T){ into(); work(); outo(); } return 0; }
T3:
题意大概是给定n,然后对于任意数1<=k<=n,选出几个数,使得这些数两两不等且可以从中选出几个等于k.
然后求最少金币数以及最少金币数的方案总数.
那么第一问很简单,贪心的可以想到1,2,4,8,...肯定最少.
于是第二问我们可以用dp求解.
我们设dp[i][j][k]为数和为i,最大数为j,数的数量为k.
然后得出方程,从f[i][j][k]转移到f[i+1][t][k+t],其中j<t<=k+1.
然后滚动数组优化空间,代码如下:
#include<bits/stdc++.h> using namespace std; #define rep(i,j,k) for (int i=j;i<=k;i++) //专属神奇循环简写 int n; int f[1005][1005]={0},dp[1005][1005]={0}; int sum=0,ans=0; inline void into(){ scanf("%d",&n); sum=int(log(n)/(log(2)))+1; } inline void work(){ dp[1][1]=1; rep(i,1,sum-1){ rep(j,1,n) rep(k,1,n) if (dp[j][k]) rep(l,k+1,j+1) f[min(n,j+l)][l]+=dp[j][k]; rep(j,1,n) rep(k,1,n) dp[j][k]=f[j][k],f[j][k]=0; } } inline void outo(){ rep(j,1,n) ans+=dp[n][j]; printf("%d %d\n",sum,ans); } int main(){ rep(i,1,1){ into(); work(); outo(); } return 0; }
T4:
题目大意:给出n个数对(ai,bi)和n个数对(xi,yi),若xi>=ai且yi>=bi,则匹配,问最大匹配数.
其中n<=10^5.
正解是贪心+set或线段树.
思路就是按xi和ai从小到大排序,然后扫一遍,将所有aj<xi的(aj,bj)找最大的bj小与yi.
那么思路很清晰,用数组模拟太慢,直接用STL自带的multiset.
代码如下:
#include<bits/stdc++.h> using namespace std; #define rep(i,j,k) for (int i=j;i<=k;i++) //专属神奇循环简写 multiset < int > tree; int n,ans=0; struct paper{ int x,y; }p[100010],q[100010]; bool cmp(paper a,paper b){ return (a.x<b.x); } inline void into(){ memset(p,0,sizeof(p)); memset(q,0,sizeof(q)); scanf("%d",&n); rep(i,1,n) scanf("%d%d",&p[i].x,&p[i].y); rep(i,1,n) scanf("%d%d",&q[i].x,&q[i].y); sort(p+1,p+1+n,cmp); sort(q+1,q+1+n,cmp); } inline void work(){ int k=1; rep(i,1,n){ while (p[i].x>=q[k].x&&k<=n){ tree.insert(q[k].y); k++; } if (tree.empty()) continue; multiset < int > :: iterator it=tree.upper_bound(p[i].y); if (it==tree.begin()) continue; ans++;it--; tree.erase(it); } } inline void outo(){ printf("%d\n",ans); } int main(){ rep(i,1,1){ into(); work(); outo(); } return 0; }
T5:
这道题最迷了.
题目就是给出n次猜数字li,ri,vi,表示[li,ri]的最小值为vi.
那么求第几次的时候会矛盾.
若n次都不矛盾就输出n+1.
那么这道题明显有一个特性:可二分性.
关于可二分性的讨论本人有另一篇blog写了:https://blog.csdn.net/hzk_cpp/article/details/78463705.
那么二分用什么来判定呢?
我们再来想想样例给我们的信息:
当我们一个大区间的最小值大于了它包含的区间的最小值,那么就不合法.
那么我们可以用数组直接模拟,大概可以O(n^2).
但是我们要更优秀的算法.
我们可以让按照最小值排序,一定让小的在前.
然后再判断后面的区间有没有包含前面的区间.
若有就r=mid,否则l=mid.
那么我们要处理的就是一个问题,如何快速判断区间包含关系.
那么并查集可好用了.
那么就是这样写了:
#include<bits/stdc++.h> using namespace std; int n,T,ans,fa[1000010]; struct node{ int l,r,x; }p[1000010],e[1000010]; bool cmp(node a,node b){ return a.x>b.x; } int find(int x){ if (x==fa[x]) return x; return fa[x]=find(fa[x]); } bool check(int k){ for (int i=1;i<=n+1;i++) fa[i]=i; for (int i=1;i<=k;i++) p[i]=e[i]; sort(p+1,p+1+k,cmp); int lmax=p[1].l,lmin=p[1].l,rmax=p[1].r,rmin=p[1].r; for (int i=2;i<=k;i++){ if (p[i].x<p[i-1].x){ if (find(lmax)>rmin) return true; for (int j=find(lmin);j<=rmax;j++) fa[find(j)]=find(rmax+1); lmin=lmax=p[i].l; rmin=rmax=p[i].r; }else{ lmin=min(lmin,p[i].l); lmax=max(lmax,p[i].l); rmin=min(rmin,p[i].r); rmax=max(rmax,p[i].r); if (find(lmax)>rmin) return true; } } if (find(lmax)>rmin) return true; return false; } inline void into(){ scanf("%d%d",&n,&T); for (int i=1;i<=T;i++) scanf("%d%d%d",&e[i].l,&e[i].r,&e[i].x); } inline void work(){ int l=1,r=T; ans=T+1; while (l<=r) { int mid=(l+r)>>1; if (check(mid)) { ans=mid; r=mid-1; }else l=mid+1; } } inline void outo(){ printf("%d\n",ans); } int main(){ into(); work(); outo(); return 0; }
T6:
水题,意思就是给出一个括号序列(只含'('或')'),然后求改变最少次使所有括号得到匹配.
数据保证n<=10^5且是偶数.
那么这道题很简单,大概用一种贪心的思路.
我们把字符串扫一遍,若扫到一个右括号没有匹配,就将它改为左括号.
最后设剩余的左括号数量为x,那么在修改x/2个左括号变为右括号就行了.
所以代码如下:
#include<bits/stdc++.h> using namespace std; #define rep(i,j,k) for (int i=j;i<=k;i++) //专属神奇循环简写 char c[100005]={0}; int n,sum1=0,sum2=0; inline void into(){ scanf("%s",&c); } inline void work(){ n=strlen(c); rep(i,0,n-1){ if (c[i]=='(') sum1++; else if (sum1>0) sum1--; else sum2++,sum1++; } } inline void outo(){ printf("%d\n",sum2+(sum1>>1)); } int main(){ rep(i,1,1){ into(); work(); outo(); } return 0; }这套试卷AK的很开心.