P9017 [USACO23JAN] Lights Off G

前言#

困了一下午, 仅仅只搞懂了个大概, 我们赶紧把这些题补了, 冷静一点

思路#

观察大样例可以发现, 答案好像都不大
容易证明的是先用最多 n 次关闭所有开关, 然后在 2n 次打开每个灯, 这样一定不超过 3n 次就可以成功的打开所有灯
那么我们考虑以这个为突破口, 枚举操作次数解决问题

那么考虑对于一种操作次数 m , 怎样判断其是否可行

首先你需要知道操作的小转化 : 对于每个 2 操作, 我们发现其相当于一个 , 那么我们可以利用 的交换律和自反律, 得出 a 的初始状态和 b 的变换是互相不影响的两个部分, 下面我们只考虑 b 变换的异或积

稍微观察一下第 i 次操作的性质, 你发现最终第 i 次操作会覆盖到 mi+1 大小的区间, belike :
m - i + 1

那么对于 m 次操作, 相当于一串长度为 1,2,3m 的区间取反

这个时候问题就更加符合中国宝宝的体质, 每次操作次数 mm+1 , 转化过来仅仅是多了一个长为 m+1 的区间取反

这个时候看似没法处理了, 但是你发现即使是多组测试数据, 但是 n 都是相通的, 进一步思考可以发现, 我们可以考虑来一个全局的预处理

考虑令 fi,S 表示第 i 次操作能否转化到 S , 其中 fi,S{0,1}

容易发现每次其实就是从 fi1,S 通过新增加一个长为 i 的区间取反变换而来的

我们可以容易的列出柿子

fi,Sfi1,SS,|S|=i

即可


感觉有点混乱, 我们再理一遍思路

首先你观察操作, 发现 a 的初始状态和 b 的变换是两个部分, 可以分开讨论

观察大样例发现答案很小, 我们考虑证明出答案 3n , 然后枚举答案(及操作次数)

假设操作次数为 m

稍微观察一下第 i 次操作的性质, 你发现最终第 i 次操作会覆盖到 mi+1 大小的区间, belike :
m - i + 1

那么对于 m 次操作, 相当于一串长度为 1,2,3m 的区间取反

这个时候问题就更加符合中国宝宝的体质, 每次操作次数 mm+1 , 转化过来仅仅是多了一个长为 m+1 的区间取反

然后我们考虑全局预处理

结束


复习#

考虑复习

  • 定义操作 (约束) 和开销 / 收益, 要求最值化开销 / 收益
    • 先排除无效元素
    • 将约束条件数学化
    • 模拟操作情况, 贪心处理最好开销 (将简单情况先处理, 然后在基础上处理最值 / 简化操作)
    • 考虑操作对答案的影响 (推式子) , 据此对操作进行处理
      • 推导每个元素对答案的贡献 (拆贡献)
      • 推导动态规划
    • 判断是否存在操作满足约束
      • 模拟, 对于重复情况去重
    • 要求一个数列的多个部分 (前缀, 子串 ) 的成本
      • 贪心找到最优操作的构造方法, 加上优化 / 找共同点
    • 找到所有情况统一的构造方案
      • 然后逐操作贪心
题意

给定长为 nn0101a,ba, b
一次操作定义为

  • bb 的一位异或 11
  • aa 异或上 bb
  • bb 循环右移一位

求最少多少次操作使得 aa 变成全 00

肯定要先模拟操作

重要性质

不难发现每次都把 aa 异或上 bb 这个操作
例如 ab1b2bka \oplus b_1 \oplus b_2 \oplus \cdots \oplus b_k , 可以简单地化成 a(b1b2bk)a \oplus (b_1 \oplus b_2 \oplus \cdots \oplus b_k)

也就是说, 问题简化为

  • bb 的一位异或 11
  • kk 异或上 bb
  • bb 循环右移一位

k=ak = a 的最小次数

不难发现对于每一个 a,ba, b , 次数的最小化问题实际上是比较确定的, 因为操作并未留下人为调整的空间

发现 b 的变化可以看做简单的滚动之后翻转
但是其异或和怎么变换?

不妨设操作次数为 k , 如果没有每次操作的翻转 b 一位, 那么对于 b 中的每一个 1 , 就等效于一个长为 k 的区间取反
加上每次操作的翻转 b 一位, 也就是多了一个长为 ki+1 的区间取反
m - i + 1

那么你发现, 问题简化为判定类
对于操作次数 k , 求是否存在 |b| 个长为 k 的和 k 个长度分别为 1k 的区间, 对这些区间取反之后, 使得 ba

其中 |b| 个长为 k 的区间是固定的, 可以提前处理变成 b
进一步简化为 k 个长度分别为 1k 的区间, 对这些区间取反之后, 使得 ba
求最小的可行 k

发现 n20 , 那可以用很经典的 O(n2n) 算法
fi,B 表示 i 次操作之后 b 的可行性
直接模拟不太好做, 巧妙地一点是只要一个 B 可以被表示为 b 进行 k 个长度分别为 1k 的区间取反, 就一定可行
所以每次加上一个长为 i 的区间取反即可, 很有生活

至于多测, 简单地值域处理一般化即可

实现#

难点不在实现

#include <bits/stdc++.h>
const int MAXVAL = 2e6 + 10;

int t, n;
int p[MAXVAL]; // 同族元素
int f[45][MAXVAL]; // 递推数组
std::string a, b;

/*旋转操作*/
int rotate(int x) { 
    return ((x & 1) << n - 1) | (x >> 1);
}

signed main()
{
    scanf("%d %d", &t, &n);
    memset(p, -1, sizeof p);
    for (int i = 0; i < (1 << n); i++) {
        int x = i;
        while (!(~p[x])) p[x] = i, x = rotate(x);
    }
    f[0][0] = 1;
    for (int i = 1, s = 0; i <= 2 * n; i++) {
        s ^= (1 << ((i - 1) % n));
        for (int j = 0; j < (1 << n); j++) f[i][p[j]] |= f[i - 1][p[j ^ s]];
    }
    while (t--)
    {
        std::cin >> a >> b;
        int A = 0, B = 0;
        for (int i = 0; i < n; i++) A |= ((a[i] - '0') << (n - i - 1)), B |= ((b[i] - '0') << (n - i - 1));
        if (A == 0) { puts("0"); continue; }
        for (int i = 1; ; i++) {
            A ^= B, B = rotate(B);
            if (f[i][p[A]]) { printf("%d\n", i); break; }
        }
    }
    return 0;
}

总结#

对于 01 串的操作, 我们考虑用数学语言表示
善于利用运算律简化问题

当观察到答案较小时, 考虑

  • 分讨答案
  • 枚举答案检查

玩样例可以找到一些有用的性质

如果多组测试数据有相通之处, 也许你可以全局的处理?

感觉这个题到紫了, 很难 (现在看还好)

枚举确定长度串的小 trick

模拟操作情况, 贪心处理最好开销 (将简单情况先处理, 然后在基础上处理最值 / 简化操作)

posted @   Yorg  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示