首页 > 其他分享 >[题目记录]loj#560 Menci的序列

[题目记录]loj#560 Menci的序列

时间:2025-01-07 16:10:47浏览次数:1  
标签:loj res ++ tot pos 560 int Menci 进位

loj#560 Menci的序列

题意

给出一个长为 \(n\) , 由 +* 组成的序列和常数 \(k\) . 对于一个这样的序列 , 定义其权值为 :

  • 初始权值为 0 , 从左到右遍历序列
  • 如果当前位是 + 就把权值\(+1\)
  • 如果当前位是 * 就把权值 \(\times 2\)
  • 对 \(2^k\) 取模 .

求原序列的一个子序列 , 最大化其权值 , 以二进制形式输出 .

\(n,k\le 10^6\)


题解

先考虑部分分 : 所有 + 之间都有 * . 考虑如果加入一个 * , 一个 + , 相当于在原数后插入的一个 \(1\) , 如果单独加入一个 * , 相当于在原数后插入了一个 \(0\) . 因此把每个 * 看做可以设置一个二进制位 , 如果后面有 + 表示可以在这一位上放置 \(1\) .

考虑贪心 , 因为有 \(k\) 位数限制 , 如果能填满 \(k\) 位 , 优先在高位放置 \(1\) ; 如果不能填满 \(k\) 位 , 优先最大化位数 .

因此从前向后考虑 , 有 \(1\) 就放 , 如果是 \(0\) 考虑能否填满 , 具体地 , 设当前限制为 \(k\) , 当前位数 ( 从后向前数 ) 为 \(pos\) :

  • \(pos\ge k\) , 后面保证可以填满 , 就把 \(1\) 放在第 \(k\) 位上 , 更新限制 \(k\gets k-1\) , 向后进行 .
  • \(pos<k\) , 最大化位数 , 就把 \(1\) 放在第 \(pos\) 位上 , 更新限制 \(k\gets pos-1\) .

考虑一般的情况 , 仍然延续部分分的思路 , 把 * 后的的看作可以放在这一位上的数 , 不同的是这个数可能 \(>1\) , 这时会出现进位的问题 .

这时候 , 为了接近部分分的做法 , 可以想到把每一位转化成 \(1\) , 进而联想到用类似进位的思路来处理 .

注意到 , 因为每个*+ 都是可选的 , 如果直接进位会出现问题 , 比如 *++ 如果变成 +* , 会少一种情况 : *+ , 也就是 \(\times 2 +1\) , 这提示我们从一个序列的子序列可以表示的运算入手简化问题 .

对于每个 * 后 , 设有 \(k\) 个 + , 表示的意义是 $x\times 2+ w\ (w\le k) $ .

而进位后表示的是 \((x+w_1)\times 2 + w_2 \ (w_1\le \frac{k}{2} , w_2 \le k \mod 2)\)

其中 , 如果表示的 \(w\) 是偶数 , 那么 \(k\) 是可以放心进位的 .

如果表示的 \(w\) 是奇数 , 那么如果 \(w_2\) 不能取到 \(1\) , 说明 \(k\) 是偶数 , 就会少情况 .

解决办法也显然了 , 如果 \(k\) 是偶数 , 不完全进位 , 在当前位留一个 \(2\) , 这样可以把每一位可选值简化到 \(0/1/2\) , 而不会少情况 .

考虑每一位取值为 \(0/1/2\) 时 , 怎样贪心地确定每一位 .

假设当前位 \(pos\) 可取 \(0/1/2\) , 主要讨论是否有可能进位 :

  • 如果 \(pos\ge k\) , 那么贡献 \(2\) 是不优的 . 在第 \(k\) 位填 \(1\) , 更新限制 \(k\gets k-1\) .

    因为在能填满的情况下 , 填了 \(2\) 后会产生一个空位 , 后面的要么连续填 \(2\) 用进位补上 , 要么填不了 \(2\) , 会让本来能连续填 \(1\) 的中间产生一个 \(0\) 变得更劣 .

  • 如果 \(pos<k\) , 那么要尽可能提高最高位 , 在 \(pos\) 位填 \(2\) , 相当于在 \(pos+1\) 位填 \(1\) . 更新限制 \(k\gets pos\) , 表示当前位 \(pos\) 还可以填数 .

类似地 , 如果当前位 \(pos\) 可取 \(0/1\) , 且 \(pos<k\) , 存在一种情况 , 下一位是 \(2\) , 把\(1\)填到 \(pos\) , 进位到 \(pos+1\) , 将最高位提高一位 , 因此这种情况下更新限制变为 \(k\gets pos\) , 其他不变 .

注意答案要处理进位 .


点击查看代码
#include <bits/stdc++.h>
#define file(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define ll long long
#define INF 0x7fffffff
#define INF64 1e18
using namespace std;

constexpr int N = 1e6 + 5;

int n, k;

string s;
int tot;

int a[N], res[N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> k;
    cin >> s;
    s = '*' + s;
    n++;
    tot = 0;

    for (int i = n; i; i--) {
        if (s[i - 1] == '+') {
            a[tot]++;
        } else
            tot++;
    }

    for (int i = 0; i <= tot; i++) {
        if (a[i] >= 3) {
            a[i]--;
            a[i + 1] += a[i] / 2;
            a[i] %= 2;
            a[i]++;
        }
    }

    while (a[tot + 1]) {
        if (a[tot + 1] >= 3) {
            a[tot + 1]--;
            a[tot + 2] += a[tot + 1] / 2;
            a[tot + 1] %= 2;
            a[tot + 1]++;
        }

        tot++;
    }

    for (int i = tot; i >= 0; i--) {
        if (a[i] == 0)
            continue;

        if (a[i] == 1) {
            if (i >= k - 1)
                res[k - 1]++, k--;
            else
                res[i]++, k = i + 1;
        }

        if (a[i] == 2) {
            if (i >= k - 1)
                res[k - 1]++, k--;
            else
                res[i + 1]++, k = i + 1;
        }
    }

    int tag = 0;

    for (int i = 0; i <= n; i++)
        if (res[i] >= 2)
            res[i + 1] += res[i] / 2, res[i] %= 2;

    for (int i = n; i >= 0; i--)
        if (res[i] == 0) {
            if (tag)
                cout << 0;
        } else {
            cout << 1;
            tag = 1;
        }

    if (!tag)
        cout << 0;

}

总结

这道题最有趣的部分就是简化问题的一部分 , 首先在部分分做法和二进制进位习惯的双端引导下 , 想到直接进位处理 , 然后发现有 Hack 的办法 . 此时优先考虑改进原做法保证正确性 , 因此仔细分析 Hack 的问题出在哪里 , 发现是因为表示的情况缺失 , 进一步从表示的所有情况出发梳理问题 , 改进方法就十分显然了 . 思路其实很自然 .

集训期间写的题解思路不够清晰 , 因为是看完做法后强行生成的思路 . 故重新整理 .

标签:loj,res,++,tot,pos,560,int,Menci,进位
From: https://www.cnblogs.com/youlv/p/18657844

相关文章

  • 5600万的代价换来的觉醒:网红“醉鹅娘”的降本自救之路
    最近听了一期关于“醉鹅娘”创业的播客,十年饮冰,难凉热血,最终做成了大网红,但背后的故事却让人警醒:负债5600万!这个数字一出来,相信不少人会倒吸一口凉气。辛辛苦苦创业十年,好不容易成了“网红”,怎么还欠了这么多钱?她的经历,不是个例,反而折射出许多创业者,乃至我们在职场中都容易陷入......
  • 5600万的代价换来的觉醒:网红“醉鹅娘”的降本自救之路
    最近听了一期关于“醉鹅娘”创业的播客,十年饮冰,难凉热血,最终做成了大网红,但背后的故事却让人警醒:负债5600万!这个数字一出来,相信不少人会倒吸一口凉气。辛辛苦苦创业十年,好不容易成了“网红”,怎么还欠了这么多钱?她的经历,不是个例,反而折射出许多创业者,乃至我们在职场中都容易陷入......
  • LOJ #3273. 「JOISC 2020 Day1」扫除 题解
    Description平面直角坐标系上一个等腰直角三角形,维护\(4\)种操作:加入\((x,y)\)。把\(y\leql\)的点横坐标变成\(\max⁡(x,n-l)\)。把\(x\leql\)的点纵坐标变成\(\max(y,n-l)\)。查询第\(i\)个点现在的位置。\(1\leqn\leq10^9,1\leqm\leq5\times10^5,1\le......
  • Clojure语言的编程范式
    标题:函数式编程之美:探索Clojure语言的独特魅力在计算机科学的浩瀚星海中,函数式编程如同一颗璀璨的明珠,以其独特的美学和强大的表达力吸引着无数程序员的目光。而在众多函数式编程语言中,Clojure犹如一颗新星,以其简洁、优雅和高效的特点,在编程世界中熠熠生辉。本文将带领读者......
  • ssm网络商城系统56077(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、项目背景随着互联网技术的普及和电子商务的快速发展,网络购物已成为人们日常生活的重要组成部分。网络商城系统作为电子商务的核心平台,其重要性......
  • SM5604btnCGRK
    usingClsPub;usingSalien.Utility;usingSalien.Utility.SUWF;usingSystem;usingSystem.Collections.Generic;usingSystem.Data;usingSystem.Web.UI.WebControls;usingSystem.Collections;namespaceSM5604btnCGRK{publicclassSM5604btnCGRK:ISuwf......
  • SMbtn5604
    usingSalien.Utility;usingSalien.Utility.SUWF;usingSystem;usingSystem.Data;usingSystem.Web.UI.WebControls;namespaceSM5604btnTJ{publicclassSM5604btnTJ:ISuwfBus{privateSlnSuwfPage_page;publicenumAction......
  • SM5604勾选的主键和实际主键可能不一致
    这是一个bug,看到勾选中是第一行,实际对应的主键可能是第2行。所见不是所得。勾选的主键与实际的主键不一样。如果不一样,就要把实际的主键设置成一样。$('#btnSCHC,#btnMXCF').click(function(){var$checked=$("td[colname='MC']input[type='checkbox']:checked")......
  • Arduino LINX 实现上拉输入,并且实现对应VI以及C#调用(以MEGA2560PRO为例)
    固件部分思路:Arduino本身可以设置INPUT_PULLUP,而LINX中没有。猜测原因是LINX在具体实现中将PINMODE设置为INPUT,并且没有实现INPUT_PULLUP版本。因此只要修改LINX固件,增加PULLUP版本的实现即可。(如果不需要普通的浮空输入,直接把源代码里的INPUT改成INPUT_PULLUP即可,无须后续操作,这......
  • 【外设篇】STMG4芯片-Hal库-I2C通信AS5600编码器(基础工程)
    引言:AS5600为绝对值编码器,其接口有I2C和ADC两种,为配合FOC的10KHZ运行速率,博主使用I2C的DMA模式+高速波特率1MHZ或ADC模拟的方式读取电机电角度,并讲明绝对值编码器在PMSM电机里如何让电角度对齐正确角度,最后用STM32Cubemx和keil5实习代码。1.I2C的HAL库函数及ADC的HAL库函数......