1. 程式人生 > 其它 >[噼昂!]stone(Pending)

[噼昂!]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\) 級別。

另外,取模與位運算拼在一起,可以嘗試使用值域分割處理,同一值域內我們可以只用考慮餘數。