[噼昂!]stone(Pending)


\[\color{red}{\text{校长者,真神人也,左马桶,右永神,会执利笔破邪炁,何人当之?}} \\ \begin{array}{|} \hline \color{pink}{\text{The principal is really a god}} \\ \color{pink}{\text{with a closestool on the left and Yongshen on the right}} \\ \color{pink}{\text{holding a sharp pen to pierce the truth}} \\ \color{pink}{\text{Who can resist him? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{green}{\text{校長は本当に神であり、左側にトイレ、右側にヨンシェンがあり}} \\ \color{green}{\text{鋭いペンを持って真実を突き刺している。誰が彼に抵抗できるだろうか? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{lightblue}{\text{Le principal est vraiment un dieu}} \\ \color{lightblue}{\text{avec des toilettes à gauche et Yongshen à droite}} \\ \color{lightblue}{\text{tenant un stylo pointu pour percer la vérité}} \\ \color{lightblue}{\text{Qui peut lui résister ? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{purple}{\text{Der Direktor ist wirklich ein Gott}} \\ \color{purple}{\text{mit einer Toilette links und Yongshen rechts}} \\ \color{purple}{\text{der einen spitzen Stift hält}} \\ \color{purple}{\text{um die Wahrheit zu durchdringen.}} \\ \color{purple}{\text{Wer kann ihm widerstehen? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{cyan}{\text{Principalis deus est, Yongshen a dextris cum latrina}} \\ \color{cyan}{\text{acuto stylo ad perforandum veritatem: quis resistet ei? }} \\ \hline \end{array} \\ \color{red}{\text{对曰:“无人,狗欲当之,还请赐教!”}} \\ \newcommand\bra[1]{\left({#1}\right)} \newcommand\Bra[1]{\left\{{#1}\right\}} \newcommand\dx[0]{\text{dx}} \newcommand\string[2]{\genfrac{\{}{\}}{0pt}{}{#1}{#2}} \newcommand\down[2]{{#1}^{\underline{#2}}} \newcommand\ddiv[2]{\left\lfloor\frac{#1}{#2}\right\rfloor} \newcommand\udiv[2]{\left\lceil\frac{#1}{#2}\right\rceil} \newcommand\lcm[0]{\operatorname{lcm}} \newcommand\set[1]{\left\{{#1}\right\}} \newcommand\ceil[1]{\left\lceil{#1}\right\rceil} \newcommand\floor[1]{\left\lfloor{#1}\right\rfloor} \]


壹、关于题目 ¶

没时间,不一定补上。

贰、关于题解 ¶

◆ 结论 の 开端

将条件翻译为,谁取到最后一个石头,谁就获胜。

考察一堆石子的情况,显然若 \(n=k(x+1)\),那么一定是必败态了,因为无论如何后手都可以维护 \(n'\) 使得 \((x+1)\mid n'\),即若先手拿出 \(t\),那么后手拿出 \((x+1-t)\) 那么多即可。其他情况,一定是必胜态,因为它可以直达必败态。

现在石子堆数变多,我们可以先将 \(n_i=k_i(x+1)+r_i\)\(k_i(x+1)\) 部分不看,因为必胜者是一定可以维护这个东西的,我们只需要看剩下的 \(r_i\).

显然,我们刨除的部分 \(k_i(x+1)\) 一定是必败态了,所以,在剩下的 \(r_i\) 中,谁取出这个部分的最后一个石子就是赢家,因为该步之后达到必败态。

所以问题变成了:

在共 \(m\)\(r_i\) 石子中,每个人可以任取石子(因为这是余数,\(r_i<x\)),取出最后一个石子的人时赢家,问先后手谁能够赢得对局?

这不就是 \(\rm Nim\) 游戏吗......

所以,先手赢的充要条件为

\[\bigoplus_{i=1}^n n_i\bmod x=0 \]

这个东西可以 \(\mathcal O(n^2)\) 地做。

◆ 优化使用

取模的本质是下取整加减法,这两个东西都和位运算不是很配。

我们先将取模部分写出来,其实就是 \(n_i-\ddiv{n_i}{x}x\),对于后者,我们不难发现,\(\forall y\in [kx,(k+1)x),\ddiv{y}{x}\equiv k\),更进一步,\(\forall y_1,y_2\in [kx,(k+1)x),\ddiv{y_1}{x}x=\ddiv{y_1}{x}x=kx\).

这提示我们可以对值域进行分割,即针对 \(k\) 分开计算。同时预判一下,这个复杂度实际上并非 \(\mathcal O(n^2)\),它是 \(\mathcal O\bra{\frac{n}{1}+\frac{n}{2}+\cdots+\frac{n}{n}}\approx \mathcal O(n\ln n)\). 该复杂度仍然可以接受。

往值域分块方向走,显然得定义 \(c_i=\sum_{j}[r_j=i]\),现在我们的问题就是,如何快速计算

\[\bigoplus_{i=kx}^{(k+1)x-1} [2\nmid c_i](i-kx) \]

更普遍一点地,我们想要计算一个值域区间 \([l,r]\) 中所有数 \(x\) 减去 \(l\) 的异或和。

不妨定义 \(f(i,j)\) 表示 \([i,i+2^j)\) 区间中的数减去 \(i\) 之后的异或和,于是可以得到转移式

\[f(i,j)=f(i,j-1)\oplus f(i+2^{j-1},j-1)\oplus (cnt(i+2^{j-1},i+2^j-1)\times 2^{j-1}) \]

其中 \(cnt(l,r)\) 表示 \([l,r]\) 的数中个数 \(\bmod 2\).

答案计数,实际上也差不多,每次倍增跳一跳,看看该步后面的数个数是否为奇数,若是,那么少算了该步长的值,将该值异或上去即可。

最后复杂度大概为 \(\mathcal O(n\ln n\log n)\),输出比较大,推荐使用较快的输出方式。

叁、参考代码 ¶

#pragma GCC optimize("Ofast")

#include <bits/stdc++.h>
using namespace std;
 
// # define USING_STDIN
// # define NDEBUG
// # define NCHECK
#include <cassert>
 
namespace Elaina {

#define rep(i, l, r) for(int i = (l), i##_end_ = (r); i <= i##_end_; ++i)
#define drep(i, l, r) for(int i = (l), i##_end_ = (r); i >= i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define whole(v) ((v).begin()), ((v).end())
#define bitcnt(s) (__builtin_popcount(s))
#ifdef NCHECK
# define iputs(Content) ((void)0)
# define iprintf(Content, argvs...) ((void)0)
#else
# define iputs(Content) fprintf(stderr, Content)
# define iprintf(Content, argvs...) fprintf(stderr, Content, argvs)
#endif
 
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair <int, int> pii;
    typedef pair <ll, ll> pll;
    
    template <class T> inline T fab(T x) { return x < 0 ? -x : x; }
    template <class T> inline void getmin(T& x, const T rhs) { x = min(x, rhs); }
    template <class T> inline void getmax(T& x, const T rhs) { x = max(x, rhs); }
 
#ifndef USING_STDIN
    inline char freaGET() {
# define BUFFERSIZE 1 << 17
        static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
        return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
# undef BUFFERSIZE
    }
# define CHARGET freaGET()
#else
# define CHARGET getchar()
#endif
    template <class T> inline T readret(T x) {
        x=0; int f = 0; char c;
        while((c = CHARGET) < '0' || '9' < c) if(c == '-') f = 1;
        for(x = (c^48); '0' <= (c = CHARGET) && c <= '9'; x = (x << 1) + (x << 3) + (c ^ 48));
        return f ? -x : x;
    }
    template <class T> inline void readin(T& x) { x = readret(T(1)); }
    template <class T, class... Args> inline void readin(T& x, Args&... args) {
        readin(x), readin(args...);
    }

    template <class T> inline void writc(T x, char s = '\n') {
        static int fwri_sta[55], fwri_ed = 0;
        if(x < 0) putchar('-'), x = -x;
        do fwri_sta[++fwri_ed] = x % 10, x /= 10; while(x);
        while(putchar(fwri_sta[fwri_ed--] ^ 48), fwri_ed);
        putchar(s);
    }

} using namespace Elaina;

const int maxn = 5e5;
const int logn = 20;

int n, lgn;
int f[maxn + 5][logn + 1];
bool buc[maxn + 5], pre[maxn + 1];
inline void input() {
    readin(n); int a;
    lgn = 31 - __builtin_clz(n);
    rep(i, 1, n) buc[readret(1)] ^= 1;
}
inline void prelude() {
    rep(i, 1, n) pre[i] = pre[i - 1] ^ buc[i];
    rep(j, 1, lgn) rep(i, 0, n - (1 << j) + 1) {
        f[i][j] = f[i][j - 1] ^ f[i + (1 << j >> 1)][j - 1];
        if(pre[i + (1 << j) - 1] ^ pre[i + (1 << j >> 1) - 1])
            f[i][j] ^= 1 << j >> 1;
    }
}

char ans[maxn * 6 + 5], *head = ans;
inline void solve() {
    rep(x, 1, n) {
        int y = x + 1, r, len, cur, xsum = 0;
        for(int l = 0; l < n; l += y) {
            r = min(l + y, n + 1) - 1;
            len = r - l + 1, cur = l;
            for(int j = lgn; ~j; --j) if(len >> j & 1) {
                xsum ^= f[cur][j], cur += 1 << j;
                if(pre[r] ^ pre[cur - 1]) xsum ^= 1 << j;
            }
        }
        if(xsum) memcpy(head, "Alice ", 6), head += 6;
        else memcpy(head, "Bob ", 4), head += 4;
    }
    fwrite(ans, 1, head - ans, stdout);
}

signed main() {
    // freopen("stone.in", "r", stdin);
    // freopen("stone.out", "w", stdout);
    input();
    prelude();
    solve();
    return 0;
}

肆、关键 の 地方 ¶

有时候真的感觉调和级数很神奇,看似暴力然而居然是 \(\ln\) 级别。

另外,取模与位运算拼在一起,可以尝试使用值域分割处理,同一值域内我们可以只用考虑余数。

posted @ 2021-10-08 15:57  Arextre  阅读(49)  评论(0编辑  收藏  举报