題目
傳送門 to XJOI(需要賬號)
題目概要
博弈規則如下:
- 一堆石子輪流取,取完者勝。
- 第一次可以取任意多個,甚至取完。但不能不取。
- 從第二次開始,取的數量都不能超過上一次(對手的那一次),但不能不取。
顯然先手必勝。但是第一次最少取多少個能夠必勝呢?石子數爲 時,這個問題的答案爲 。
現在,咱們只需要求出 就行了! 是給定的!
沒必要輸出辣麼長的數字,咱們取模 再輸出吧。
數據範圍與約定
對於 的數據,
對於 的數據,
思路
先找找 的規律。注意到在 爲奇數時,先手取一個石子就能獲勝——以後二者都只能一個一個的拿。那麼,在 爲偶數時,不會有人想要後手得到 爲奇數的情況。更確切地說,二者每次拿走石子的數量都是偶數。所以,把兩個石頭捆綁
在一起,我們得到
遞歸終點是 爲奇數,所以我們可以直接寫出
對於 的數據,可以 整除分塊。 是用來計算 的前綴和的。
若想得到更高分,枚舉可能的 的取值。記 ,那麼答案就是
爲什麼是這個式子?因爲 ,所以 。
說白了,我們強硬的給其中一個數增添 這個因數。
但是也可能超過 啊?所以我們減去 來修正。
計算 很快,計算 需要整除分塊 ,所以我們整理一下,得到
時間複雜度呢?顯然應該是
所以總複雜度是 的。儘管我仍然過不了?大概是因爲太卡常,所以只多給了五分吧
代碼
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long int_;
inline int readint() {
int a = 0; char c = getchar(), f = 1;
for(; c<'0' or c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c and c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
void writeint(int_ x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) writeint(x/10);
putchar((x%10)^48);
}
# define MB template < typename T >
MB void getMax(T &a,const T &b){ if(a < b) a = b; }
MB void getMin(T &a,const T &b){ if(b < a) a = b; }
# define FOR(i,n) for(int i=0; i<(n); ++i)
const int Mod = 998244353;
int g(int_ n){
int_ res = 0;
for(int_ l=1,r; l<=n; l=r)
r = n/(n/l)+1, res += (r-l)*(n/l);
return res%Mod;
}
int main(){
int_ n, ans = 0; cin >> n;
for(int k=1; (1ll<<k)<=n; ++k){
ans += (1ll<<k>>1)%Mod*g(n/(1ll<<k))%Mod;
if(ans >= Mod) ans -= Mod;
}
cout << (ans+g(n))%Mod << endl;
return 0;
}