[ARC135F] Delete 1, 4, 7, ... (数学,倍增思想)

神题%%%,可以考虑看这篇

题意:给定 \(n,k\) ,问对于一个 \([1,2,\dots,n]\) 的数组,每次删去模 \(3\)\(1\) 的位置,求 \(k\) 次操作后数组剩下的数的和

\(n \leq 10^{14},k \leq 100\)

考虑一次删除后,第 \(i\) 个数的值为 \(f(i)\)

不难讨论得出 \(f(x)=\lfloor \frac{3x+1}{2} \rfloor\)

那么 \(k\) 次删除后的值为 \(f^k(x)\)

注意到删一次会剩下 \(\lfloor\frac{2n}{3}\rfloor\) 个数,那么暴力遍历所有剩下的位置计算,\(O(k{(\frac{2}{3})}^kn)\)

\(k\) 小的时候过不去……

手动模拟下过程找找性质,发现这个过程会筛得只剩下 \(2^k\) 个同余类\(\pmod {3^k}\)

也可以归纳证明这个式子:

\[\begin{align} f^k(x+2^k)&=f^{k-1}(\lfloor \frac{3x+3\cdot 2^k+1}{2} \rfloor)\\ &=f^{k-1}(\lfloor \frac{3x+1}{2} \rfloor + 3\cdot 2^{k-1})\\ &=f^{k-1}(\lfloor \frac{3x+1}{2} \rfloor) + 3 \cdot 3^{k-1}\\ &=f^k(x)+3^k \end{align} \]

也就是:

\[f^k(x+i2^k)=f^k(x)+i3^k \]

那么这个函数可以快速平移,拼上暴力算法,复杂度变成

\(O(k\min({(\frac{2}{3})}^kn,2^k))\)

但是 \(k\) 的大小中等时又过不去……

神奇的地方来了,考虑类似于折半搜索样的方法

\(k=l+r\) ,尽量分得平均

\[f^k(x+i2^r)=f^{l+r}(x+i2^r)=f^l(f^r(x)+i3^r) \]

对外层使用倍增 ST 表样的方法求和,这样的好处是利用函数可以平移任意 \(2^l\) 倍数的长度的性质快速计算

具体的,把 \(x\in [1,2^r]\) 枚举掉记为 \(a\) ,记 \(g(a,j)=\sum_{i=0}^{2^j-1} f^l(a+i3^r)\)

依照定义:

\[g(a,j)=g(a,j-1)+g(a+2^{j-1}3^r,j-1) \]

\(a\) 过大的时候直接按照 \(2^l\) 平移再递归计算(否则 T 飞),然后还要记忆化

最后由于我们求得总是一个前缀,但是我们又可以把枚举的 \(a\)\(2^r\) 平移

那么对每一个 \(a\) 求出最大的可能的 \(j\) ,把这一段前缀倍增掉求和即可

#include <cstdio>
#include <unordered_map>
#pragma GCC optimize(2,3,"Ofast")
using namespace std;
typedef long long ll;
const int P=998244353;
ll n,res;int k;
ll f(ll x,int i){
	for(;i;--i) x=(3*x+1)/2;
	return x;
}
ll o[103],w[103],msk;
int L,R;
unordered_map<ll,int> mp[103];
int g(ll x,int i){
	if(x-1>msk) return (g(((x-1)&msk)+1,i)+(((x-1)>>L)%P)*(w[L]%P)%P*((1ll<<i)%P)%P)%P; //对内层的 a 进行 2^l 的平移
	if(!i) return mp[i][x]=f(x,L)%P;
	if(mp[i].find(x)!=mp[i].end()) return mp[i][x];
	return mp[i][x]=(g(x,i-1)+g(x+w[R]*(1ll<<(i-1)),i-1))%P;
}
int main(){
	scanf("%lld%d",&n,&k);
	o[0]=n;
	for(int i=1;i<=k;++i) o[i]=o[i-1]*2/3;
	if(k>40){//是的……需要数据分治……毒瘤
		int res=0;
		for(int i=1;i<=o[k];++i)
			res=(res+f(i,k))%P;
		printf("%d\n",res);
		return 0;
	}
	L=k/2;R=k-L;w[0]=1;msk=(1ll<<L)-1;
	for(int i=1;i<=30;++i) w[i]=w[i-1]*3;
	for(int i=1;i<=o[k]&&i<=(1ll<<R);++i){
		ll c=f(i,R),upb=((o[k]-i)>>R)+1;
		for(int j=50;~j;--j)
			if(upb>>j&1){
				res=(res+g(c,j))%P;
				c+=w[R]<<j; //对外层的 a 进行 2^r 的平移,拼接成一段前缀
			}
	}
	printf("%lld\n",res);
	return 0;
}

总结一下,对于可以对函数段进行快速平移的求和/求积,利用分治/DP/倍增等技巧拼接答案

好像许多组数数论题都是这个技巧(exlucas?)

posted @ 2022-02-24 21:21  yyyyxh  阅读(173)  评论(0编辑  收藏  举报