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