【考试反思】联赛模拟测试11
每日挂分 喜闻乐见
T3: 20 \(\rightarrow\) 0
T1:One
改编过的约瑟夫问题。不同的是每次出局的编号是变化的。直接暴力模拟依据美观程度 \(O(n^2)\) 到 \(O(n\log n)\) 不等(反正都是 60 分)。
那么考虑正解。我们现在知道的是最后剩下的人是谁,求最初的编号,是逆推。我们可以将所有编号 \(-1\),即 0 1 2 ... n-1
,第 \(i\) 轮就是上次游戏后重新编号后,选择第 \(i-1\) 号人出局。
我们只需要考虑如何从这一轮推到上一轮的编号。用 \(x\) 表示最后剩下的人在当前编号状态下的编号,那么递推式子就是:
(\(x\) +上一个状态下要出局的人的编号 (\(n-i+1\))) \(\text{mod}\) 上一状态剩下的人的数量(\(i\))
如果不懂,可以正推一下,看新一轮是如何编号的。注意是逆推的,所以是上次出局的人是 \(n-i+1\)。这里说的枚举顺序都是从 \(2\) 开始到 \(n\) 的,如果枚举 \(1\) 到 \(n-1\) 也类似,改改式子就行了。
边界是 \(x=0\),因为最后只剩他一个人。最后记得要 \(+1\),因为题目的编号是 \(1\) 到 \(n\) 的。
#include <bits/stdc++.h>
using namespace std;
const int maxn=3e3+10;
int n;
inline int read(){
int x=0;bool fopt=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')fopt=0;
for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-48;
return fopt?x:-x;
}
int main(){
#ifndef LOCAL
freopen("one.in","r",stdin);
freopen("one.out","w",stdout);
#endif
int T=read();
while(T--){
n=read();
int x=0;
for(int i=2;i<=n;i++)
x=(x+n-i+1)%i;
printf("%d\n",x+1);
}
return 0;
}
关于暴力模拟:
好像大家都用的链表。不会吧不会吧,不会有人不用 vector
吧。
#include <bits/stdc++.h>
using namespace std;
const int maxn=3e3+10;
int n;
inline int read(){
int x=0;bool fopt=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')fopt=0;
for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-48;
return fopt?x:-x;
}
vector<int> a;
int main(){
#ifndef LOCAL
freopen("one.in","r",stdin);
freopen("one.out","w",stdout);
#endif
int T=read();
while(T--){
n=read();
a.clear();
for(register int i=1;i<=n;i++)
a.push_back(i);
int j=0;
for(register int i=1;i<=n-1;i++){
j+=i-1;
if(j>=a.size())j%=a.size();
a.erase(a.begin()+j);
}
printf("%d\n",a[0]);
}
return 0;
}
关于 vector
有多快:
\(\Huge{邹队}\)太巨啦!
T2:砖块
简单模拟,怎么写都行。我的写法是记录砖块的放置方向(三种情况),两个端点的坐标。直接做就行。
#include <bits/stdc++.h>
using namespace std;
const int maxn=1000+10;
int n,Minx,Miny,Maxx,Maxy;
char opt[maxn];
int vis[maxn*2][maxn*2];
inline int read(){
int x=0;bool fopt=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')fopt=0;
for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-48;
return fopt?x:-x;
}
int stx,sty,edx,edy,dir;
inline void Solve(char now){
if(now=='N'){
if(dir==1){
sty++;edy+=n;dir=2;
for(int i=sty;i<=edy;i++)
vis[stx][i]++;
}else if(dir==2){
sty+=n;edy++;dir=1;
vis[stx][sty]++;
}else if(dir==3){
sty++;edy++;dir=3;
for(int i=stx;i<=edx;i++)
vis[i][sty]++;
}
}else if(now=='S'){
if(dir==1){
sty-=n;edy--;dir=2;
for(int i=sty;i<=edy;i++)
vis[stx][i]++;
}else if(dir==2){
sty--;edy-=n;dir=1;
vis[stx][sty]++;
}else if(dir==3){
sty--;edy--;dir=3;
for(int i=stx;i<=edx;i++)
vis[i][sty]++;
}
}else if(now=='W'){
if(dir==1){
stx-=n;edx--;dir=3;
for(int i=stx;i<=edx;i++)
vis[i][sty]++;
}else if(dir==2){
stx--;edx--;dir=2;
for(int i=sty;i<=edy;i++)
vis[stx][i]++;
}else if(dir==3){
stx--;edx-=n;dir=1;
vis[stx][sty]++;
}
}else{
if(dir==1){
stx++;edx+=n;dir=3;
for(int i=stx;i<=edx;i++)
vis[i][sty]++;
}else if(dir==2){
stx++;edx++;dir=2;
for(int i=sty;i<=edy;i++)
vis[stx][i]++;
}else if(dir==3){
stx+=n;edx++;dir=1;
vis[stx][sty]++;
}
}
if(stx<Minx)Minx=stx;
if(edx<Minx)Minx=edx;
if(stx>Maxx)Maxx=stx;
if(edx>Maxx)Maxx=edx;
if(sty<Miny)Miny=sty;
if(edy<Miny)Miny=edy;
if(sty>Maxy)Maxy=sty;
if(edy>Maxy)Maxy=edy;
}
inline void Init(){
stx=sty=edx=edy=1000;
Minx=Miny=0x3f3f3f3f;
Maxx=Maxy=-1;
dir=1;
memset(vis,0,sizeof(vis));
}
int main(){
#ifndef LOCAL
freopen("block.in","r",stdin);
freopen("block.out","w",stdout);
#endif
int T=read();
while(T--){
Init();
n=read();
scanf("%s",opt+1);
int len=strlen(opt+1);
vis[stx][sty]++;
for(int i=1;i<=len;i++){
Solve(opt[i]);
}
int Max=0;
for(int i=Minx;i<=Maxx;i++)
for(int j=Miny;j<=Maxy;j++)
if(vis[i][j]>Max)Max=vis[i][j];
if(stx==edx){
int len=edy-sty+1;
for(int i=1;i<=len;i++)
printf("%d ",stx-1000);
puts("");
for(int i=sty;i<=edy;i++)
printf("%d ",i-1000);
puts("");
}else{
int len=edx-stx+1;
for(int i=stx;i<=edx;i++)
printf("%d ",i-1000);
puts("");
for(int i=1;i<=len;i++)
printf("%d ",sty-1000);
puts("");
}
printf("%d\n",Max);
}
return 0;
}
T3:数学
高精神仙数学题,目前还不会。
注意只维护后三位的方法显然是错误的。10000
的阶乘就是反例。正确答案是 008
。
python
算阶乘的方法:
import math
math.factorial(10000)
T4:甜圈
吉司机线段树?Segment tree beats?不懂。
考场写了个分块,居然比暴力还慢,震惊。考场上的思路是维护所加值和处理次数 \(times\) 的 \(\cfrac{(times+1)times}{2}\) 是否相等,似乎和正解差的有点远,导致想不出来。
正解显然线段树,那么我们考虑如何维护。
我们需要知道的是整个区间的所有值是否相同,因为首先显然不同值的甜甜圈不能一起做这个任务。那么我们只需要维护当前区间的最大值和最小值就行了,如果最大最小值相等显然就是所有值都相等的合法区间,直接赋值。不合法的区间,直接 return
掉。至于是否是执行了 \(k\) 次任务,最后一起查询即可。
那么这样就可以获得和暴力一个分的好成绩。因为数据中存在合法和非法区间一个个交替,每次修改操作会使合法的继续合法,非法的继续非法的情况。解决方法就是若线段树当前节点的左右两个儿子中一个合法,一个不合法,那么视这个大区间是合法的。
时间复杂度需要势能分析,不会,总之均摊 \(O(n\log n)\) 就完了。
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int n,K,ans;
inline int read(){
int x=0;bool fopt=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')fopt=0;
for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-48;
return fopt?x:-x;
}
#define lson (rt<<1)
#define rson (rt<<1|1)
int Min[maxn<<2],Max[maxn<<2],lazy[maxn<<2],vis[maxn<<2];
inline void pushdown(int rt,int l,int r){
if(lazy[rt]){
Min[lson]=Min[rson]=Max[lson]=Max[rson]=lazy[rt];
lazy[lson]=lazy[rson]=lazy[rt];
lazy[rt]=0;
}
}
inline void pushup(int rt){
if(vis[lson]&&vis[rson]){//震惊,一开始没写这种情况,居然过了
vis[rt]=1;
}else if(!vis[lson]&&!vis[rson]){
Min[rt]=min(Min[lson],Min[rson]);
Max[rt]=max(Max[lson],Max[rson]);
}else if(vis[lson]||vis[rson]){
//如果只写这句不写第一句,好像会把两个儿子都是非法区间的区间弄成合法的,不过数据过水过掉了
Min[rt]=(!vis[lson])?Min[lson]:Min[rson];
Max[rt]=(!vis[lson])?Max[lson]:Max[rson];
}else vis[rt]=1;
}
void modify(int rt,int l,int r,int s,int t,int val){
if(vis[rt]==1)return;
if(Min[rt]==Max[rt]&&s<=l&&r<=t){
if(val-1!=Min[rt])return vis[rt]=1,void();//要求任务是连续的
Min[rt]=Max[rt]=val;
lazy[rt]=val;
return;
}
pushdown(rt,l,r);
int mid=(l+r)>>1;
if(s<=mid)modify(lson,l,mid,s,t,val);
if(t>mid)modify(rson,mid+1,r,s,t,val);
pushup(rt);
}
int query(int rt,int l,int r,int p){
if(vis[rt]==1)return 0;
if(l==r)return Min[rt]==K?1:0;//听说数据锅了,导致如果判<K会错
pushdown(rt,l,r);
int mid=(l+r)>>1;
if(p<=mid)return query(lson,l,mid,p);
else return query(rson,mid+1,r,p);
}
int main(){
#ifndef LOCAL
freopen("deco.in","r",stdin);
freopen("deco.out","w",stdout);
#endif
n=read();K=read();
int T=read();
while(T--){
int l=read(),r=read(),c=read();
modify(1,1,n,l,r,c);
}
for(int i=1;i<=n;i++)
ans+=query(1,1,n,i);
printf("%d\n",ans);
return 0;
}
官方思路:
将每个点都视为一个字符串,不妨对每个字符串哈希。
如果最终哈希值等于要求的哈希值,那么合法。
区间修改哈希值,直接用线段树维护乘法加法标记就可以了。
但是感觉比维护最大最小值麻烦点,就没写。(毕竟线段树 2 当初调了好久)