首页 > 其他分享 >[NOIP2020] 移球游戏 解题报告

[NOIP2020] 移球游戏 解题报告

时间:2023-06-18 14:33:34浏览次数:45  
标签:cnt 颜色 int tot 移球 解题 place NOIP2020 empty

题目描述

给定 \(n+1\) 个栈,栈的高度限制为 \(m\)。初始时前 \(n\) 个上每个有 \(m\) 个球,最后一个为空。球分为 \(n\) 种颜色,每种恰好 \(m\) 个。一次操作可以把一个栈顶的元素弹出放到一个另一个栈顶,但是不可以使栈溢出或下溢。现要把同种颜色的球移动到同一个栈上,你需要构造一个在 \(820000\) 次操作内的方案。

题目分析

题目总共分为 \(4\) 个 \(subtask\) 解决。

Sub1

这个部分 \(n\le 2,m\le 20\),通过模拟发现:
两个满栈 \(1,2\) 与一个空栈 \(3\) 可以实现将一个栈中的颜色聚拢在一起,并放在栈底。具体操作为统计栈 \(1\) 中颜色为 \(1\) 的球的个数 \(cnt\),从栈 \(2\) 中弹出 \(m-cnt\) 个球放入栈 \(3\) 中,再把栈 \(1\) 中颜色为 \(1\) 的球弹出放入 \(3\) 中,颜色为 \(2\) 的球弹出放入栈 \(2\) 中,最后将栈 \(3\) 中的球全部弹出放入栈 \(1\) 中,这样栈 \(1\) 中颜色为 \(1\) 球就到了栈 \(1\) 的栈底了。同理可将颜色为 \(2\) 的球放到栈 \(2\) 的底端。这样一次操作代价为 \(3 \times m - cnt\) 步。反复交换执行两个操作,直到两个栈均满足条件。

期望 \(10pts\),实际 \(10pts\)。

Sub2

这个部分 \(n\le 10,m\le 20\),显然还是可以使用刚才的方法,将两个栈 \(1,2\) 换成栈 \(X,Y\) 即可。 每次操作选择两个未满足条件的栈,进行上述操作,直到所有栈都满足条件。对于其中任意的连续两次操作要求存在 \(x_i\not=x_{i+1}\) 或 \(y_i\not= y_{i+1}\),否则后一次操作会无效,凭空增大步数。可以使用队列完成选取过程。

期望 \(25pts\),实际 \(45pts\),测试点中步数最多为\(1.6M\)。

点击查看代码

namespace sub2{
    int count(int p){//记录一个栈p中有多少个颜色非p的球
        int cnt=0;
        for(int i=1;i<=m;i++){
            if(a[p][i]!=p){
                cnt++;
            }
        }
        return cnt;
    }
    int check(int p){//判断一个栈是否符合条件
        for(int i=1;i<=m;i++){
            if(a[p][i]!=p){
                return 0;
            }
        }
        return 1;
    }
    void work(int x,int y){//对两个栈进行操作
        int num=count(x);
        for(int i=1;i<=num;i++){
            ans.pb(mp(y,n+1));
            a[n+1][++tot[n+1]]=a[y][tot[y]--];//Y中弹出相应个数的球
        }
        while(num){/*因为目的是将颜色为x的球放到最底下,所以下面没有其它球的颜色为x的球不需要弹出
                    这是个重要的优化,25->45*/
            if(a[x][tot[x]]!=x){//颜色非x的球弹入Y中
				num--;
                ans.pb(mp(x,y));
                a[y][++tot[y]]=a[x][tot[x]];
            }
            else{//否则弹入Z中
                a[n+1][++tot[n+1]]=a[x][tot[x]];
                ans.pb(mp(x,n+1));
            }
            tot[x]--;
        }
        while(tot[n+1]){//弹回
            a[x][++tot[x]]=a[n+1][tot[n+1]--];
            ans.pb(mp(n+1,x));
        }
    }
    void solve(){
        queue<int>q;//用队列维护还未符合条件的栈
        for(int i=1;i<=n;i++){
            if(!check(i)){
                q.push(i);
            }
        }
        while(q.size()!=1){
            int x=q.front();
            q.pop();
            int y=q.front();
            work(x,y);//对队列最前的两个栈进行操作
            if(!check(x)){//不符合条件的栈才放回队列中
                q.push(x);
            }
        }
    }
}

Sub3

这个部分 \(n\le 50,m\le 85\ \text{和}\ m\le 300\)。回到上一个做法,我们发现主要消耗步数的地方在于不停的将重复执行某个操作 \((x,y)\)。其实,颜色为 \(x\) 的球不一定要全部放在栈 \(X\) 中,只要所有颜色为 \(x\) 的球上面都没有其它颜色的球,这样所有的球都可以放入空栈中,再将栈中剩余球数最小的栈清空,这样步数就会减少许多。

那如何将球移到栈顶呢?只需要将步骤改动一下,放到 \(Y\) 栈顶的不是非颜色 \(c\) 的球,是颜色为 \(c\) 的球。将非颜色 \(c\) 的球弹进空栈 \(Z\) 中,最后 \(Z\) 中先弹 \(tot_Z-cnt\) 个球到 \(X\) 中,\(Y\) 将最上方的颜色为 \(c\) 的球弹回 \(X\) 中,最后将 \(Z\) 中剩余的 \(cnt\) 个球弹入 \(Y\) 中,这样就完成了“拉起”操作。此时 \(Y\) 中球的个数与顺序均不变,\(X\) 中的颜色为 \(c\) 的球均在栈顶,一次“拉起”操作步数为 \(2\times(m+cnt)\)。

期望 \(70pts\),实际 \(80pts\),测试点中步数最多的为 \(900K\)。

点击查看代码

namespace sub3{
    bool f,b[60];//b[i]=1表示栈i已经符合条件
    int empty_place;//记录空栈位置
    int count(int p,int c){//统计栈p中颜色为c的球的个数
        int cnt=0;
        for(int i=1;i<=m;i++){
            if(a[p][i]==c){
                cnt++;
            }
        }
        return cnt;
    }
    void push_up(int p,int c){//“拉起”操作
        if(b[p]||p==empty_place){
            return;
        }
        int cnt=count(p,c),y=(p+1)%(n+2);
        if(cnt==0){
            return;
        }
        if(cnt==m){
            f=b[p]=1;
            return;
        }
        while(y==empty_place||y==p||y==0){
            y=(y+1)%(n+2);
        }//找到一个可以用的栈Y
        for(int i=1;i<=cnt;i++){
            ans.pb(mp(y,empty_place));
            a[empty_place][++tot[empty_place]]=a[y][tot[y]--];
        }//弹出Y顶端cnt个球
        int num=cnt;
        while(num){
            if(a[p][tot[p]]==c){
                num--;
                ans.pb(mp(p,y));
                a[y][++tot[y]]=a[p][tot[p]--];
            }
            else{
                ans.pb(mp(p,empty_place));
                a[empty_place][++tot[empty_place]]=a[p][tot[p]--];
            }
        }//弹到X中没有颜色为c的球
        while(tot[empty_place]>cnt){
            ans.pb(mp(empty_place,p));
            a[p][++tot[p]]=a[empty_place][tot[empty_place]--];
        }//将栈X填到只剩cnt个空
        for(int i=1;i<=cnt;i++){
            ans.pb(mp(y,p));
            a[p][++tot[p]]=a[y][tot[y]--];
        }//将颜色为c的球填回栈顶
        while(tot[empty_place]){
            ans.pb(mp(empty_place,y));
            a[y][++tot[y]]=a[empty_place][tot[empty_place]--];
        }//Z中剩下的球全部填回Y中
    }
    void solve(){
        empty_place=n+1;
        for(int i=1;i<=n;i++){
            f=0;
            for(int j=1;j<=n+1;j++){
                push_up(j,i);
            }
            if(f){
                continue;
            }
            for(int j=1;j<=n+1;j++){
                if(j==empty_place||b[j]){
                    continue;
                }
                while(a[j][tot[j]]==i){
                    ans.pb(mp(j,empty_place));
                    a[empty_place][++tot[empty_place]]=a[j][tot[j]--];
                }
            }//将在栈顶的所有颜色为i的球全部放入空栈中
            int num=0;
            for(int j=1;j<=n+1;j++){
                if(j==empty_place||b[j]){
                    continue;
                }
                if(!num||tot[j]<tot[num]){
                    num=j;
                }
            }
            for(int j=1;j<=n+1;j++){
                if(j==num){
                    continue;
                }
                while(tot[j]<m){
                    ans.pb(mp(num,j));
                    a[j][++tot[j]]=a[num][tot[num]--];
                }
            }//清空球数最少的栈
            empty_place=num;
        }
    }
}

Sub4

还有 \(90K\) 的步数怎么消掉呢?再次回到方案上,发现新的方案耗步数最多的地方在于不停地回弹,那能不能不回弹呢,答案是可以的。发现若 \(Y\) 里面没有颜色为 \(c\) 的球(后文称为“白栈”),那么将颜色为 \(c\) 的球弹入 \(Y\) 中后,\(Y\) 中的颜色为 \(c\) 的球就全在栈顶了,若将 \(X\) 中剩余的球全部弹入 \(Z\) 中,栈 \(X\) 变成了空栈,栈 \(Z\) 变成了白栈。目的仍然达成,且栈中情况基本不变,仍是 \(1\) 白栈,\(1\) 空栈,消耗步数为 \(m+cnt\)。

构造白栈方法也很简单,选择一个不符合条件的栈 \(X\),将其中的颜色为 \(c\) 的球全部“拉起”,再放入栈 \(Z\) 中,从 \(Y\) 中弹出相应个数的颜色不为 \(c\) 的球到 \(X\) 中,再将 \(Z\) 中的球弹入 \(Y\) 中。步数消耗为 \(4\times(m+cnt)\)。

期望得分 \(100pts\),实际 \(100pts\),测试点中步数最多的点为 \(700K\) 步。

点击查看代码

namespace sub4{
    int empty_place,last,b[60],f;//last:白栈
    int count(int p,int c){
        int cnt=0;
        for(int i=1;i<=m;i++){//同上
            if(a[p][i]==c){
                cnt++;
            }
        }
        return cnt;
    }
    void make(int p,int c){//造白栈
        int cnt=count(p,c),y;
        for(int i=n+1;i>=1;i--){
            if(i==p||i==empty_place||b[i]){
                continue;
            }
            y=i;
            break;
        }//随意找一个栈当中介
        for(int i=1;i<=cnt;i++){
            ans.pb(mp(y,empty_place));
            a[empty_place][++tot[empty_place]]=a[y][tot[y]--];
        }
        while(tot[p]){
            if(a[p][tot[p]]==c){
                ans.pb(mp(p,y));
                a[y][++tot[y]]=a[p][tot[p]--];
            }
            else{
                ans.pb(mp(p,empty_place));
                a[empty_place][++tot[empty_place]]=a[p][tot[p]--];
            }
        }
        while(tot[empty_place]>cnt){
            ans.pb(mp(empty_place,p));
            a[p][++tot[p]]=a[empty_place][tot[empty_place]--];
        }
        for(int i=1;i<=cnt;i++){
            ans.pb(mp(y,p));
            a[p][++tot[p]]=a[y][tot[y]--];
        }
        while(tot[empty_place]){
            ans.pb(mp(empty_place,y));
            a[y][++tot[y]]=a[empty_place][tot[empty_place]--];
        }//拉起p中颜色为c的球
        for(int i=1;i<=cnt;i++){
            ans.pb(mp(p,empty_place));
            a[empty_place][++tot[empty_place]]=a[p][tot[p]--];
        }
        while(tot[p]<m){
            if(a[y][tot[y]]==c){
                ans.pb(mp(y,empty_place));
                a[empty_place][++tot[empty_place]]=a[y][tot[y]--];
            }
            else{
                ans.pb(mp(y,p));
                a[p][++tot[p]]=a[y][tot[y]--];
            }
        }
        while(tot[empty_place]){
            ans.pb(mp(empty_place,y));
            a[y][++tot[y]]=a[empty_place][tot[empty_place]--];
        }//弹掉p中的颜色为c的球
        last=p;
    }
    void push_up(int p,int c){//将栈p中的颜色为c的球拉到栈顶,哪个栈不重要
        if(b[p]||p==empty_place||p==last){
            return;
        }
        int cnt=count(p,c);
        if(cnt==0||cnt==m){
            return;
        }
        for(int i=1;i<=cnt;i++){
            ans.pb(mp(last,empty_place));
            a[empty_place][++tot[empty_place]]=a[last][tot[last]--];
        }
        while(tot[p]){
            if(a[p][tot[p]]==c){
                ans.pb(mp(p,last));
                a[last][++tot[last]]=a[p][tot[p]--];
            }
            else{
                ans.pb(mp(p,empty_place));
                a[empty_place][++tot[empty_place]]=a[p][tot[p]--];
            }
        }
        last=empty_place;
        empty_place=p;
    }//将p中的颜色为c的球弹到白栈中,空栈变为白栈,栈p变为空栈
    void solve(){
        empty_place=n+1;
        int num;
        for(int i=1;i<=n;i++){
            last=num=f=0;
            for(int j=1;j<=n+1;j++){
                if(!b[j]&&j!=empty_place){
                    num=j;
                    break;
                }
            }//寻找可以变为白栈的栈
            make(num,i);
            for(int j=1;j<=n+1;j++){
                if(j==empty_place){
                    continue;
                }
                push_up(j,i);
            }
            for(int j=1;j<=n+1;j++){
                if(j==empty_place||b[j]){
                    continue;
                }
                while(a[j][tot[j]]==i){
                    ans.pb(mp(j,empty_place));
                    a[empty_place][++tot[empty_place]]=a[j][tot[j]--];
                }
            }//将所有颜色为i的球放入空栈
            b[empty_place]=i;//标记符合条件
            num=0;
            for(int j=1;j<=n+1;j++){
                if(j==empty_place||b[j]){
                    continue;
                }
                if(tot[j]<m){
                    num=j;
                    break;
                }
            }
            for(int j=1;j<=n+1;j++){
                if(j==num||b[j]){
                    continue;
                }
                while(tot[j]<m){
                    ans.pb(mp(num,j));
                    a[j][++tot[j]]=a[num][tot[num]--];
                }
            }
            empty_place=num;//清空一个栈
        }
    }
}

标签:cnt,颜色,int,tot,移球,解题,place,NOIP2020,empty
From: https://www.cnblogs.com/luoshen0/p/17489108.html

相关文章

  • [网络安全] DVWA之 Command Injection 攻击姿势及解题详析合集
    CommandInjection命令注入(CommandInjection)是一种安全漏洞,发生在应用程序使用用户提供的输入作为系统命令的一部分而未经充分验证和过滤的情况下。当应用程序在构造系统命令时,如果没有对用户输入进行适当的验证和过滤,攻击者可以通过在用户输入中插入恶意命令来执行任意系统命......
  • CF521E Cycling City 解题报告
    题面一道难得恰到好处的构造题。分析因为要构造三条从\(s\)到\(t\)的路径,且三条路径中任意两条路径经过的点集的交集等于\(\{s,t\}\)。我们知道当两条路径经过的点集的交集等于\(\{s,t\}\)时,这两条路径将会构成一个环。因此题意转化为要求我们找到两个经过的边集有重合......
  • NOIP2020 T2 字符串匹配【题解】
    NOIP2020T2字符串匹配首先声明这篇题解存在大多数让我这种人看懂的废话,如果想要速通,请另寻他解题目简化定义字符串乘法为\(AB\)为把两个字符串拼起来,定义阶乘\(A^i\)表示\(\prod_{1}^iA\)再定义\(F(S)\)为\(S\)中出现奇数次字符的数量现给定一个字符串\(S\),求......
  • [网络安全] DVWA之CSRF攻击姿势及解题详析合集
    CSRFCSRF(Cross-SiteRequestForgery,跨站请求伪造)是一种常见的Web应用程序安全漏洞,它利用了用户在已认证的网站中的身份,通过欺骗用户发起非预期的请求。攻击者会构造一个恶意网页,使用户在浏览器中访问该网页时,自动向目标网站发送了未经用户授权的请求。CSRF攻击的原理是利用了W......
  • [网络安全] DVWA之 Insecure CAPTCHA 攻击姿势及解题详析合集
    InsecureCAPTCHACAPTCHA(CompletelyAutomatedPublicTuringtesttotellComputersandHumansApart,全自动区分计算机和人类的图灵测试)是一种常用的人机验证机制,旨在防止恶意机器人或自动化程序对网站进行滥用或攻击。reCAPTCHA验证流程如下:网站集成:网站管理员在网站上集......
  • 「解题报告」HDU6358 Innocence
    其实挺简单的,但是考场上状态太差没推出来,暴力还挂了。麻了。首先看题:发现,这不是我们异或FWT的题吗,下次出题记得标明出处容易发现,我们实际上要求的就是集合幂级数\([x^k](x^l+x^{l+1}+\cdots+x^{r-1}+x^r)^n\)。考虑直接手动模拟FWT。设\(F(l,r)=FWT(x^l+......
  • 「解题报告」CF1738H Palindrome Addicts
    神秘回文串题。首先容易发现要求的是区间本质不同回文串个数,所以直接上论文做法即可。容易想到增量构建回文自动机,假如现在建出了\([1,r]\)的PAM,考虑有多少回文串出现在了\([l,r]\)内。考虑记录每个回文串的最后一次出现位置\(last_p\),那么这个串的左端点就是\(last_p......
  • [网络安全] DVWA之 Open HTTP Redirect 攻击姿势及解题详析合集
    Lowlevel主页面如下:点击Quote1,发现url传递参数源代码审计源码如下:<?phpif(array_key_exists("redirect",$_GET)&&$_GET['redirect']!=""){header("location:".$_GET['redirect']);exit;}http......
  • 「解题报告」CF1815E Bosco and Particle
    好像不难。但是没想到。首先这玩意看起来就得拆开,要不然完全做不了。假如我们只考虑某一个点\(i\),考虑\(i-1\toi,i\toi+1\)这两条边的经过次数,不难发现其它的点是不会影响这两条边的。那么我们可以直接依据题意模拟,只考虑这一个点的周期是多长,然后所有的周期\(\mat......
  • [ABC303G] Bags Game 解题分析
    1题目大意1.1题目翻译有两个人轮流取物品。总共有\(n\)个物品,第\(i\)个物品的价值为\(w_i\)。他们按照下面的其中一种方式取物品:取出这一排物品最前面的或者最后面的。这一步没有代价。设还剩下\(m\)个物品,那么重复取出\(\min(B,m)\)个物品,每次取出最前面的......