1. 程式人生 > 其它 >【CF1601F】Two Sorts(Meet in Middle)

【CF1601F】Two Sorts(Meet in Middle)

題目連結

  • 定義 \(a_{1\sim n}\) 為將 \(1\sim n\) 按字典序從小到大排序後的結果,求 \((\sum_{i=1}^n(i-a_i)\ \operatorname{mod}\ 998244353)\ \operatorname{mod}\ 10^9+7\)
  • \(1\le n\le10^{12}\)

題意轉化

這題的求和中有兩種不同的取模,看起來非常麻煩。

考慮取模的一個經典轉化,即 \(x\ \operatorname{mod}\ y=x-\lfloor\frac xy\rfloor\times y\)

所以原式可以寫成:\((\sum_{i=1}^ni-a_i-\lfloor\frac{i-a_i}{998244353}\rfloor\times998244353)\ \operatorname{mod}\ 10^9+7\)

發現 \(\sum i-a_i=0\),所以要求的就是 \(\sum_{i=1}^n\lfloor\frac{i-a_i}{998244353}\rfloor\)

Meet in Middle

考慮 Meet in Middle,列舉最高的 \(6\) 位,則最高 \(6\) 位為列舉值的數肯定在 \(a\) 中是連續的一段。

對於一個長度為 \(6+l\)、較低位在所有可能的較低位中字典序排名為 \(rk\) 的數 \(y\),假設前 \(6\) 位數為 \(x\)、前 \(6\) 位小於 \(x\) 的數共有 \(ct\) 個,則它產生的貢獻應該是 \(\lfloor\frac{(ct+rk)-(x10^l+y)}{998244353}\rfloor\)

,可以拆成只與前 \(6\) 位和 \(l\) 有關的 \(ct-x10^l\) 以及只與較低位有關的 \(rk-y\)

形如 \(\lfloor\frac{A+B}C\rfloor\) 的式子有個經典拆分方式:\(\lfloor\frac{A+B}C\rfloor=\lfloor\frac AC\rfloor+\lfloor\frac BC\rfloor+\lfloor\frac {(A\ \operatorname{mod}\ C)+(B\ \operatorname{mod}\ C)}C\rfloor\)

\(\lfloor\frac AC\rfloor\)\(\lfloor\frac BC\rfloor\)

分別只與 \(A,B\) 有關可以各自計算,而 \(\lfloor\frac {(A\ \operatorname{mod}\ C)+(B\ \operatorname{mod}\ C)}C\rfloor\) 在已知 \(A\) 情況下只要求有多少 \(B\ \operatorname{mod}\ C\) 大於等於 \(C-(A\ \operatorname{mod}\ C)\),可以事先拿一個 vector 存下所有 \(B\ \operatorname{mod}\ C\) 並排序,詢問時 lower_bound 一下即可。

因此先 dfs 一遍所有可能的較低位,分 \(l\) 存下 \(rk-y\) 並排序。

然後再 dfs 一遍所有可能的最高 \(6\) 位,不足 \(6\) 位直接統計,否則根據預先存下的資訊計算。

實現起來可能要注意一些細節,感覺我的寫法似乎有點麻煩。

程式碼:\(O(\sqrt n\log \sqrt n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define SZ 1000000
#define V 998244353
#define X 1000000007
#define LL long long
#define Dec(x) (!x--&&(x+=X))
using namespace std;
LL n,nn,p;int ans,tn[7],c[7][7],s[7][7][SZ+1];
void Init(RI x,RI l,CI k)//預處理較低位,存下所有p-x
{
	s[k][l][++c[k][l]]=(++p)-x;if(l==k) return;for(RI i=0;i<=9;++i) Init(x*10+i,l+1,k);
}
void BF(LL x,RI l)//暴力
{
	if(x>n) return;++p;ans=(ans+(p-x)/V-(p<x)+X)%X;for(RI i=0;i<=9;++i) BF(x*10+i,l+1);
}
void Solve(RI x,RI l)//列舉最高6位
{
	if(l^6) {++p,ans=(ans+(p-x)/V-(p<x)+X)%X;for(RI i=0;i<=9;++i) Solve(x*10+i,l+1);return;}//不足6位,直接統計
	if(x==nn) return BF(x,0);//最高6位與n的最高6位相同,直接暴力
	RI i,k;LL v;for(k=0,v=x;k<=6&&v<=n;++k,v*=10);--k;//求出較低位最大長度
	for(i=0;i<=k;++i) v=p-1LL*x*tn[i],ans=(ans+1LL*(v/V-(v<0)+X)*tn[i])%X,//列舉長度,加上的數為p-x*tn[i]
		v=(v%V+V)%V,ans=(ans+tn[i]-(lower_bound(s[k][i]+1,s[k][i]+tn[i]+1,V-v)-s[k][i])+1)%X;//求相加大於等於998244353的數的個數
	for(i=0;i<=k;++i) p+=tn[i];//更新排名
}
int main()
{
	RI i,k,dc;for(scanf("%lld",&n),tn[0]=i=1;i<=6;++i) tn[i]=tn[i-1]*10;
	for(k=0;k<=6;++k) for(p=0,Init(0,0,k),i=0;i<=k;++i) sort(s[k][i]+1,s[k][i]+tn[i]+1);//排序
	if(p=0,n<1e6) for(i=1;i<=9;++i) BF(i,1);else {nn=n;W(nn>=1e6) nn/=10;for(i=1;i<=9;++i) Solve(i,1);}//n小直接暴力,否則nn記錄前6位
	return printf("%d\n",(int)(1LL*(X-ans)*V%X)),0;//答案為-ans*998244353
}