[噼昂!]stone(Pending)
壹、关于题目 ¶
没时间,不一定补上。
贰、关于题解 ¶
◆ 结论 の 开端
将条件翻译为,谁取到最后一个石头,谁就获胜。
考察一堆石子的情况,显然若 \(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\) 游戏吗......
所以,先手赢的充要条件为
这个东西可以 \(\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]\),现在我们的问题就是,如何快速计算
更普遍一点地,我们想要计算一个值域区间 \([l,r]\) 中所有数 \(x\) 减去 \(l\) 的异或和。
不妨定义 \(f(i,j)\) 表示 \([i,i+2^j)\) 区间中的数减去 \(i\) 之后的异或和,于是可以得到转移式
其中 \(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\) 级别。
另外,取模与位运算拼在一起,可以尝试使用值域分割处理,同一值域内我们可以只用考虑余数。