题目
传送门 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;
}