[luogu8340]山河重整

记$S$中的元素依次为$a_{1}<a_{2}<...<a_{k}$,考虑对合法的条件进行转化——

结论:$S$合法当且仅当$\begin{cases}\sum_{i=1}^{k}a_{i}\ge n&(1)\\\forall i\in [1,k],\sum_{j=1}^{i-1}a_{j}+1\ge a_{i}&(2)\end{cases}$

必要性:若$(1)$不满足则无法得到$n$,若$(2)$不满足则无法得到$\sum_{i=1}^{j-1}a_{j}+1$

充分性:对于$x\in [1,n]$,从后往前枚举$i$并在$x\ge a_{i}$时将$x$减去$a_{i}$,归纳$x\le \sum_{j=1}^{i}a_{j}$即可

推论:$S$合法当且仅当$\forall x\in [1,n],S$中$\le x$的元素和$\ne x-1$

必要性:若$S$中存在$x$的后继$a_{i}$则$\sum_{j=1}^{i-1}a_{j}+1=x<a_{i}$,不存在则$\sum_{i=1}^{k}a_{i}=x-1<n$

充分性:若$(1)$不满足则取$x=\sum_{i=1}^{k}a_{i}+1$,若$(2)$不满足则取$x=\sum_{j=1}^{i-1}a_{j}+1$

枚举最小的$x$(不满足条件),记对应的方案数为$f_{x}$,则转移即
$$
f_{x}=\{[1,x]子集和为x-1的方案数\}-\sum_{x'=1}^{x-1}\{(x',x]子集和为x-x'的方案数\}f_{x'}
$$
另外,答案为$2^{n}-\sum_{x=1}^{n}2^{n-x}f_{x}$

关于前者,变形得$\sum_{i=1}^{k}a_{i}=\sum_{i=1}^{k}(a_{i}-a_{i-1})(k-i+1)$,且两者一一对应

记$g_{s}$为$\sum_{i=1}^{k}i\cdot x_{i}=s$的解数(其中$x_{i}\ge 0$且非0的$x_{i}$构成前缀),则方案数也即$g_{x}$

注意到$k\le \sqrt{2s}$,倒序枚举$k$并对$g$做完全背包(对每个$k$均设初值$g_{0}=1$),时间复杂度为$o(n\sqrt{n})$

关于后者,可以看作每次设初值为$g_{x'+kx'}=f_{x'}$时的结果,并根据$x'\le \lfloor\frac{x}{2}\rfloor$分治即可

总复杂度为$o(n\sqrt{n})$,可以通过

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define N 500005
 4 #define ll long long
 5 int n,mod,ans,pw[N],g[N],f[N];
 6 void add(int &x,int y){
 7     x+=y;if (x>=mod)x-=mod;
 8 } 
 9 void solve(int n){
10     if (n==1)return;
11     solve(n>>1);
12     memset(g,0,sizeof(g));
13     for(int i=(int)sqrt(n<<1);i;i--){
14         for(int j=1,s=i+1;(j<=(n>>1))&&(s<=n);j++,s+=i+1)add(g[s],f[j]);
15         for(int j=n;j>=0;j--)g[j]=(j<i ? 0 : g[j-i]);
16         for(int j=i;j<=n;j++)add(g[j],g[j-i]);
17     }
18     for(int i=(n>>1)+1;i<=n;i++)f[i]=(f[i]-g[i]+mod)%mod;
19 }
20 int main(){
21     scanf("%d%d",&n,&mod);
22     pw[0]=1;
23     for(int i=1;i<=n;i++)pw[i]=(pw[i-1]<<1)%mod;
24     for(int i=(int)sqrt(n<<1);i;i--){
25         g[0]=1;
26         for(int j=n;j>=0;j--)g[j]=(j<i ? 0 : g[j-i]);
27         for(int j=i;j<=n;j++)add(g[j],g[j-i]);
28     }
29     for(int i=1;i<=n;i++)f[i]=(i==1 ? 1 : g[i-1]);
30     ans=pw[n],solve(n);
31     for(int i=1;i<=n;i++)ans=(ans-(ll)pw[n-i]*f[i]%mod+mod)%mod;
32     printf("%d\n",ans);
33     return 0;
34 }
View Code

 

posted @ 2022-05-17 20:29  PYWBKTDA  阅读(87)  评论(0编辑  收藏  举报