Codeforces Round 779 div.2 D2F

D2. 388535 (Hard Version)

题意:给出两个数 \(l,r\) 和一个数字集合 \(a\),有某个 \(x\),使得 \([l,r]\) 范围内的这些数分别异或上 \(x\) 之后,结果恰好能组成序列 \(a\)。要求找到一个合法的 \(x\),保证有解。

对于每一位去考虑:如果在某一个二进制位上,两边集合0和1的个数不相等,比如 \(l...r\) 中共有3个0,2个1;序列 \(a\) 中共有2个0,3个1,那么必须要让数量对调,可以直接得出 \(x\) 在这一二进制位上必为1。不能对调则这一位上必为0

从高位到低位依次进行,每次把 \(l,r\)\(a\) 分别分为高位为0和1的四组,然后根据数量对应上,分开递归向下求解。

如果两边集合0和1的个数全部相等呢?那么在这一位上,无法确定取0还是取1,有没有可能都可以呢?

接下来证明:在最高位处,如果0和1的个数相等,不论怎么取,都不影响解的存在。

因为 \(l,r\) 是一个连续的区间,我们考虑最高位为1的分界线,其实也就是 \(l,r\) 的中点(因为要0和1一样多)

随便举个例子:

l=5   0101
6     0110
7     0111
8     1000
9     1001
r=10  1010

在最高位上,我们遇到了0和1个数相等的情况,将它们分为两组,我们发现后面的位置上数字恰好是相反的。

因为一个是从000开始加,另一个是从111开始减,当然是完全相反的。

也就是说,此时对调这两组分配给 \(a\) 的方式,仅仅只是让后续答案对调,该有解还是有解,不用担心此处取值的伏笔。另外,分配到 \(a\) 的子 \(l,r\) 仍然是连续的。我们只需要在最高位处做完之后把最高位抹去,后续仍然满足这个条件。

具体实现上,先将 \(a\) 插入字典树,然后递归向下求解会比较方便

struct node{
    int cnt;
    int ch[2];
}t[maxn << 5];
int tcnt = 1;
 
void insert(int x){
    int p = 1;
    for(int k=17;k>=0;k--){
        int v = (x >> k) & 1;
        if(!t[p].ch[v]){
            t[p].ch[v] = ++tcnt;
            t[tcnt].cnt = 0;
            t[tcnt].ch[0] = t[tcnt].ch[1] = 0;
        }
        p = t[p].ch[v];
        t[p].cnt++;
    }
}
 
int ans = 0;
void work(int p, int lv, int l, int r){
    if(lv < 0) return;
    int mid = r;
    for(int i=l;i<=r;i++){
        if((i >> lv) & 1){
            mid = i-1;
            break;
        }
    }
    if(mid-l+1 == t[t[p].ch[0]].cnt){
        if(mid >= l) work(t[p].ch[0], lv-1, l, mid);
        if(mid < r) work(t[p].ch[1], lv-1, (mid+1)^(1<<lv), r^(1<<lv));
    }else{
        if(mid >= l) work(t[p].ch[1], lv-1, l, mid);
        if(mid < r) work(t[p].ch[0], lv-1, (mid+1)^(1<<lv), r^(1<<lv));
        ans |= (1 << lv);
    }
}
 
void solve(){
    tcnt = 1;
    ans = 0;
    t[1].ch[0] = t[1].ch[1] = 0;
 
    int l, r;
    cin >> l >> r;
    for(int i=l;i<=r;i++){
        int x; cin >> x;
        insert(x);
    }
    work(1, 17, l, r);
    cout << ans << '\n';
}

E. Gojou and Matrix Game

题意:有一个 \(n\times n\) 的网格,每个格子上有一个分数 \(1...n^2\),不重复。接下来两个人玩游戏,第一名玩家首先占据 \(sx, sy\) 格,随后轮流选择格子占据,要求满足以下条件:

  • 选取的格子距离另一名玩家上次占据的格子的曼哈顿距离不超过 \(k\)
  • 可以重复占据,每次占据都能获得该格子上的分数(可重复获得)

请问对于每个起始坐标 \(sx,sy\),玩 \(10^{100}\) 轮,谁能获胜?

首先不难发现,分数是不重复的,那么分数最高的格子只有一个,谁先占到谁就赢了。

游戏规则是不能选取距对方上次选取位置 \(k\) 格以内的,双方有可能互相僵持,都占不到最高分的点

那么再去考虑次高分的位置:如果次高分的控制范围内包括了最高分,那么这也是一个先手必胜点(即使对方跟你僵持都拿不到最高,但你拿的是次高,所以也是必胜)。否则这就是先手必败点。

如此考虑下去,我们发现:对于一个包含 \(x\) 分的点来说,只要它的掌控范围内包含了所有比 \(x\) 大的必胜点,\(x\) 就是必胜点。否则为必败点。


另外再给出一种博弈论常用的思路:如果我选择了分数为 \(x\) 的点后,对方不得不选择分数小于 \(x\) 的点,那么我就是必胜的,因为我可以继续选择当前点,这个逻辑在双方都进行最优策略下是完全正确的。


那么问题就变成了如何判断。我们按照权值从大到小枚举,每遇到一个必胜点,都将它的位置丢进一个集合,只要集合中是否存在到当前枚举点距离 \(\gt k\) 的位置就可以了。

当然暴力判断怎么样都是 \(n^3\) 的,此处又有一个神奇的“放宽解空间”思想:

想判断 \(|i-i'|+|j-j'|\le k\) ,它等价于 \(max(|i+j-(i'+j')|, |i-j-(i'-j')|)\)

然后就只需要记录 \(i+j\)\(i-j\) 的最大最小值即可...

难想...

本质上是放宽了解空间,使得更容易维护

感觉这种东西都挺难发现的,记一下吧(


Codeforces Round 779 div.2 D2F
http://www.lxtyin.ac.cn/2022/03/29/题解/Codeforces Round 779 div.2 D2E/
作者
lx_tyin
发布于
2022年3月29日
许可协议