Codeforces 1076G Array Game——线段树+博弈

题意:

有一种双人游戏规则如下:n个格子排成一排,编号1~n,每个格子都有一个数值a[i],开始把骰子扔到1号格子,并把1号格子的数值减1,然后双方轮流操作,每次操作从骰子所在的格子(假设编号为x)开始,选择[x,x+m]并且数值大于0的格子,将骰子扔到这个格子上,并把这个格子的数值减1,不能操作者输

现在有两种区间操作

1 将区间[l,r]的数值加上d

2 询问在区间[l,r]进行游戏先手胜还是后手胜

1<=n<=2e5, 1<=m<=5, 1<=q<=2e5, 1<=a[i]<=1e12, 1<=d<=1e12,注意一下longlong

思路:

首先考虑这个游戏,我们用f[i]表示到第i号格子时是胜(1)还是负(0)(注意到第i号格子是指减1之后到第i号格子,假设这个位置的值原本是a[i],那么在这个位置上先手面对的数值其实是a[i]-1,这样比较好处理),那么只要[i+1,i+m]存在一个败态,我们就可以跳到这个状态把负状态给队手,也就是说此时先手必胜。若[i+1,i+m]不存在败态,那么我们要讨论(a[i]-1)的奇偶性,奇胜偶负,这个自己想一下就可以了。

然后考虑区间操作,首先不考虑修改,只考虑如何根据子区间的胜负状态合并出父区间的胜负状态,根据上面的游戏原理我们发现一个格子的状态和它后面m个格子的状态以及本身的奇偶性有关,我们暂时不考虑奇偶性,考虑这个东西:现在有一段区间,而且不知道该区间的任何状态,但是我知道区间末尾后的m个格子的状态(注意这m个格子不是区间里面的,是区间后面的),那么我们是否可以根据游戏原理推出整个区间的状态,肯定是可以的!这就提示了我们一种合并方法,我们设一个数组为node[root][s](第二维大小为1<<5),表示节点root代表的区间(默认使用线段树了)末尾之后的m个格子状态为s时前m个格子的状态,这一步有点跳跃,我们举个例子,假设当前区间是?????五个不确定的状态,m为2,末尾之后的m个格子状态为01,那么?????01推完以后就是(不考虑奇偶)1101101,前m个状态为11,也就是node[root][01] = 11,为什么要保存前m个状态呢?考虑合并两个区间,左区间尾巴后的1<<m个状态有了,那么我们就把右区间的头和左区间的尾接起来就能得到合并完后的区间状态了,代码大体上是这样的;node[root][s] = node[root<<1][ node[root<<1|1][s] ],思考一下,这样就完成了合并

然后在上面的基础上考虑上奇偶,其实奇偶只需要在初始化叶子节点时考虑,这个初始化的时候考虑一下就可以了,不麻烦,不懂的话可以看代码

区间查询操作现在也很明确了,就是普通的线段树查询

最后还有个区间修改操作,我们发现加偶数后其实区间的奇偶性不变,所以直接跳过,加奇数后区间奇偶性反转,所以等价于区间反转操作,这个我们可以在上面node[root][s]的基础上再开一维node[root][0/1][s],表示原序列和反转序列,然后在这两个序列之间swap什么的就好了,反转操作大家应该都会干,不多说了。

代码如下,搞懂pushup和init函数这题就做完了,node数组的含义上面介绍过了,所以不加注释了

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
typedef long long ll;
int n, m, q, all;
ll a[maxn];
struct Node {
    int s[1<<5];
}node[maxn<<2][2];
int lazy[maxn<<2];
void pushup(int root) {
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < all; j++) {
            node[root][i].s[j] = node[root<<1][i].s[node[root<<1|1][i].s[j]];
        }
    }
}
void rev(int root) {
    lazy[root] ^= 1;
    for (int i = 0; i < all; i++) {
        swap(node[root][0].s[i], node[root][1].s[i]);
    }
}
void pushdown(int root) {
    if (lazy[root] == 0) return;
    rev(root<<1);
    rev(root<<1|1);
    lazy[root] = 0;
}
void init(int idx, int t, int v) {
    for (int i = 0; i < all; i++) {
        if (i != all-1 || v % 2 == 1) node[idx][t].s[i] = (i>>1)+(1<<(m-1));
        else node[idx][t].s[i] = (i>>1);
    }
}
void build(int l, int r, int root) {
    if (l == r) {
        init(root, 0, (a[l]-1)%2);
        init(root, 1, ((a[l]-1)%2)^1);
        return;
    }
    int mid = (l+r)>>1;
    build(l, mid, root<<1);
    build(mid+1, r, root<<1|1);
    pushup(root);
}
void update(int l, int r, int root, int ul, int ur) {
    if (ul <= l && r <= ur) {
        rev(root);
        return;
    }
    int mid = (l+r)>>1;
    pushdown(root);
    if (ul <= mid) update(l, mid, root<<1, ul, ur);
    if (mid < ur) update(mid+1, r, root<<1|1, ul, ur);
    pushup(root);
}
Node query(int l, int r, int root, int ql, int qr) {
    if (ql <= l && r <= qr) {
        return node[root][0];
    }
    int mid = (l+r)>>1;
    pushdown(root);
    if (ql <= mid && qr <= mid) {
        return query(l, mid, root<<1, ql, qr);
    }
    else if (ql > mid && qr > mid) {
        return query(mid+1, r, root<<1|1, ql, qr);
    }
    else {
        Node nl = query(l, mid, root<<1, ql, qr);
        Node nr = query(mid+1, r, root<<1|1, ql, qr);
        Node ans;
        for (int i = 0; i < all; i++) {
            ans.s[i] = nl.s[nr.s[i]];
        }
        return ans;
    }
}
int main() {
    scanf("%d%d%d", &n, &m, &q);
    all = (1<<m);
    for (int i = 1; i <= n; i++) scanf("%I64d", &a[i]);
    build(1, n, 1);
    while (q--) {
        int op, l, r;
        ll d;
        scanf("%d%d%d", &op, &l, &r);
        if (op == 1) {
            scanf("%I64d", &d);
            if (d&1) update(1, n, 1, l, r);
        }
        else {
            Node ans = query(1, n, 1, l, r);
            if ((ans.s[all-1]>>(m-1))&1) printf("1\n");
            else printf("2\n");
        }
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值