B - CF674F Bears and Juice
Sol
最后我们得到的信息是:每头熊有没有睡觉,以及如果睡觉了是在哪一天睡的。我们可能得到的信息的种数显然是桶数量的一个上界。假设总共有 d d d天,信息种数就是:
∑ i = 0 min { p , n − 1 } d i ( n i ) \sum_{i=0}^{\min\{p,n-1\}} d^i {n\choose i} i=0∑min{p,n−1}di(in)
其中 i i i枚举的是睡觉了的熊的数量, i i i不能大于 p p p且 i i i不能等于 n n n。
这个上界是可以达到的:我们令每一桶酒对应一种情况;在第 i i i天,让第 j j j头熊喝所有满足下面条件的酒 x x x:在 x x x对应的情况中,第 j j j头熊是第 i i i天睡觉的。最后达到的情况恰好就是有酒的桶对应的情况。
所以答案就是可能得到的信息的种数。由于 p p p很小可以直接暴力算出 ( n i ) {n\choose i} (in):用 1 , 2 , ⋯ i 1,2,\cdots i 1,2,⋯i去对 n ( n − 1 ) ( n − 2 ) ⋯ ( n − i + 1 ) n(n-1)(n-2)\cdots (n-i+1) n(n−1)(n−2)⋯(n−i+1)进行约分,复杂度是 O ( i 2 log i ) O(i^2\log i) O(i2logi)。然后再 O ( p q ) O(pq) O(pq)算出答案。总复杂度 O ( p 3 log p + p q ) O(p^3\log p + pq) O(p3logp+pq)。
Code
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define PB push_back
#define MP make_pair
#define FIR first
#define SEC second
#define ll long long
#define uint unsigned
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
int gcd(int a,int b) { return b?gcd(b,a%b):a; }
uint calc_C(int n,int m) {
vector<uint> w;
for(int i=n;i>n-m;--i) w.PB(i);
for(int i=1;i<=m;++i) {
int t=i;
for(int j=0;t!=1&&j<w.size();++j) if(w[j]!=1) {
int g=gcd(w[j],t);
w[j]/=g,t/=g;
}
}
uint ans=1;
for(int j=0;j<w.size();++j) ans*=w[j];
return ans;
}
int n,p,q;
uint C[150];
uint getans(int d) {
uint ans=0,t=1;
for(int j=0;j<=p;++j)
ans+=C[j]*t,t*=d;
return ans;
}
int main() {
rd(n),rd(p),rd(q); p=min(p,n-1);
for(int i=0;i<=p;++i) C[i]=calc_C(n,i);
uint ans=0;
for(int i=1;i<=q;++i) ans^=i*getans(i);
printf("%u",ans);
return 0;
}
C - ARC091F Strange Nim
Sol
只需要对每一堆算出它的Grundy number就可以了。
通过找规律会发现,设 g k ( n ) g_k(n) gk(n)表示 K i = k K_i = k Ki=k,堆中石子的数量为 n n n的Grundy number,则
g k ( n ) = { 0 0 ≤ n < k n k k ∣ n g k ( n − ⌊ n k ⌋ − 1 ) k ∤ n g_k(n) = \begin{cases} 0 & 0\le n < k\\ {n\over k} & k \mid n \\ g_k(n-\lfloor{n\over k}\rfloor - 1) & k\nmid n \end{cases} gk(n)=⎩⎪⎨⎪⎧0kngk(n−⌊kn⌋−1)0≤n<kk∣nk∤n
令 T = max { a i } T=\sqrt {\max\{ a_i\}} T=max{ai},如果 ⌊ n k ⌋ > T \lfloor {n\over k} \rfloor > T ⌊kn⌋>T,直接递归算 g k ( n − ⌊ n k ⌋ − 1 ) g_k(n-\lfloor {n\over k}\rfloor-1) gk(n−⌊kn⌋−1);否则,跳到第一个为 k k k的倍数的或者 ⌊ n k ⌋ \lfloor{n\over k}\rfloor ⌊kn⌋变化了的 n ′ = n − t ( ⌊ n k ⌋ + 1 ) n'= n-t(\lfloor{n\over k}\rfloor + 1) n′=n−t(⌊kn⌋+1)。前一部分中至多跳 n T n\over T Tn次;后一部分中每跳一次 ⌊ n k ⌋ \lfloor{n\over k}\rfloor ⌊kn⌋的值都会改变,所以后一部分至多跳 T T T次。所以总时间复杂度 O ( ∑ a i ) O(\sum \sqrt {a_i}) O(∑ai)。
Code
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define PB push_back
#define MP make_pair
#define FIR first
#define SEC second
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int T=30000;
int calc(int n,int k) {
if(n<k) return 0;
if(n%k==0) return n/k;
int t=(n/k+1);
if(t>T) return calc(n-t,k);
int x=(n%k)/t;
if((n-x*t)%k) x++;
return calc(n-x*t,k);
}
int n,ans;
int main() {
rd(n);
for(int i=1;i<=n;++i) {
int a,k; rd(a),rd(k);
ans^=calc(a,k);
}
if(ans) printf("Takahashi");
else printf("Aoki");
return 0;
}