[XJOI趣味赛]抗疫斗争

题目

传送门 to XJOI(需要账号)

题目概要
博弈规则如下:

  • 一堆石子轮流取,取完者胜。
  • 第一次可以取任意多个,甚至取完。但不能不取。
  • 从第二次开始,取的数量都不能超过上一次(对手的那一次),但不能不取。

显然先手必胜。但是第一次最少取多少个能够必胜呢?石子数为 nn 时,这个问题的答案为 hnh_n

现在,咱们只需要求出 i=1ndihd\sum_{i=1}^{n}\sum_{d|i}h_d 就行了!nn 是给定的!

没必要输出辣么长的数字,咱们取模 998244353998244353 再输出吧。

数据范围与约定
对于 95%95\% 的数据,n5×1013n\le 5\times 10^{13}

对于 100%100\% 的数据,n1015n\le 10^{15}

思路

先找找 hnh_n 的规律。注意到在 nn 为奇数时,先手取一个石子就能获胜——以后二者都只能一个一个的拿。那么,在 nn 为偶数时,不会有人想要后手得到 nn' 为奇数的情况。更确切地说,二者每次拿走石子的数量都是偶数。所以,把两个石头捆绑在一起,我们得到 h2n=2hnh_{2n}=2h_n

递归终点是 nn 为奇数,所以我们可以直接写出 hn=max2knk=lowbit(n)h_n=\max_{2^k|n}k=lowbit(n)

对于 95%95\% 的数据,可以 O(nlogn)\mathcal O(\sqrt n \log n) 整除分块。O(logn)\mathcal O(\log n) 是用来计算 hh 的前缀和的。

若想得到更高分,枚举可能的 hh 的取值。记 g(n)=i=1nnig(n)=\sum_{i=1}^{n}\lfloor\frac{n}{i}\rfloor ,那么答案就是 k=0+2k[g(n2k)g(n2k+1)]\sum_{k=0}^{+\infty}2^k\left[g\middle(\middle\lfloor\frac{n}{2^k}\middle\rfloor\middle)-g\middle(\middle\lfloor\frac{n}{2^{k+1}}\middle\rfloor\middle)\right]

为什么是这个式子?因为 g(n)=xyn1g(n)=\sum_{xy\le n}1 ,所以 g(n2k)=xyn2k1=2kxyn1=(2kx)yn1g(\lfloor\frac{n}{2^k}\rfloor)=\sum_{xy\le\frac{n}{2^k}}1=\sum_{2^kxy\le n} 1=\sum_{(2^kx)y\le n}1

说白了,我们强硬的给其中一个数增添 2k2^k 这个因数。

但是也可能超过 2k2^k 啊?所以我们减去 g(n2k+1)g(\lfloor\frac{n}{2^{k+1}}\rfloor) 来修正。

计算 2k2^k 很快,计算 g(n)g(n) 需要整除分块 O(n)\mathcal O(\sqrt n) ,所以我们整理一下,得到 ans=g(n)+k=1+(2k2k1)g(n2k)ans=g(n)+\sum_{k=1}^{+\infty}(2^k-2^{k-1})g\left(\middle\lfloor\frac{n}{2^k}\middle\rfloor\right)

时间复杂度呢?显然应该是 k=0+n2k=nk=0+(12)k=(2+2)n\sum_{k=0}^{+\infty}\sqrt{\frac{n}{2^k}}=\sqrt n\sum_{k=0}^{+\infty}\left(\frac{1}{\sqrt{2}}\right)^k=(2+\sqrt 2)\sqrt n

所以总复杂度是 O(n)\mathcal O(\sqrt n) 的。尽管我仍然过不了?大概是因为太卡常,所以只多给了五分吧

代码

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章