【考试反思】联赛模拟测试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 当初调了好久)

posted @ 2020-10-08 14:09  Midoria7  阅读(156)  评论(1编辑  收藏  举报