[AHOI2022]山河重整

89 篇文章 0 订阅
30 篇文章 0 订阅

题目

题目背景
一生二,二生三,三生万物。然何生一焉?唯 O U Y E \sf OUYE OUYE 可之。

题目描述
求有多少个 S ⫅ [ 1 , n ] ∩ Z S\subseteqq[1,n]\cap\Z S[1,n]Z 使得
[ 1 , n ] ∩ Z ∈ { sum ( T )    ∣    T ⫅ S } [1,n]\cap\Z\in\{\text{sum}(T)\;|\;T\subseteqq S\} [1,n]Z{sum(T)TS}

其中 sum ( S ) = ∑ x ∈ S x \text{sum}(S)=\sum_{x\in S}x sum(S)=xSx 。输出对质数 M M M 取模, M M M 由输入给定。

数据范围与提示
n ⩽ 5 × 1 0 5 n\leqslant 5\times 10^5 n5×105,时限 3s \text{3s} 3s 。提示:复杂度比较高。

思路

一眼鉴定为背包问题,然后我成了 ​🤡

其他题解都看不懂,只有这位老哥的给我讲懂了。话说 ZJOI \text{ZJOI} ZJOI 题解区也有他 🤔

恰好是 [ 1 , n ] [1,n] [1,n] 肯定是有性质的啊。至少联想一下 [ 1 , n ] [1,n] [1,n] 能凑出 [ 1 , n ( n + 1 ) 2 ] [1,\frac{n(n+1)}{2}] [1,2n(n+1)] 这种老百姓结论啊。从小到大排列 x 1 , x 2 , … , x k ∈ S x_1,x_2,\dots,x_k\in S x1,x2,,xkS,则只需
1 + ∑ i = 1 α − 1 x i ⩾ x α ( α ∈ [ 1 , n ] ) 1+\sum_{i=1}^{\alpha-1}x_i\geqslant x_{\alpha}\quad(\alpha\in[1,n]) 1+i=1α1xixα(α[1,n])

证明比发现容易。于是立刻有 O ( n 2 ) \mathcal O(n^2) O(n2) 做法。优化可能要考虑根号分治,虽然这也是困难的。

{ x i } \{x_i\} {xi} 整体 + 1 +1 +1 时,很难保证上面的约束条件。必须把约束条件去掉——使用 容斥,减去不合法的情况。枚举恰好无法凑出的值,也就是说 [ 1 , v ] [1,v] [1,v] 中选择了的数的和是 ( v − 1 ) (v{-}1) (v1),且 [ 1 , v ) [1,v) [1,v) 都可以被凑出。记方案数为 f ( v ) f(v) f(v),则答案为
2 n − ∑ i = 1 n 2 n − i f ( i ) 2^n-\sum_{i=1}^{n}2^{n-i}f(i) 2ni=1n2nif(i)

现在只需求出 f f f 。显然它也得容斥递推。有
f ( v ) = choose ( 0 , v − 1 ) − ∑ i = 1 v − 1 f ( i ) ⋅ choose ( i , v − i ) f(v)=\text{choose}(0,v{-}1)-\sum_{i=1}^{v-1}f(i)\cdot\text{choose}(i,v{-}i) f(v)=choose(0,v1)i=1v1f(i)choose(i,vi)

其中 choose ( i , x ) \text{choose}(i,x) choose(i,x) 为从 ( i , + ∞ ) (i,+\infty) (i,+) 中选数使得和为 x x x 的方案数。这个东西好像是可以 O ( n 1.5 ) \mathcal O(n^{1.5}) O(n1.5) 搞定的,但是我们不可能求出所有 choose \text{choose} choose 啊?

注意到:为了使得 choose ≠ 0 \text{choose}\ne 0 choose=0,必须有 i < v 2 i<\frac{v}{2} i<2v 。毕竟 i = v i=v i=v 是不在范围内的。

又想到: choose \text{choose} choose 是背包问题,要与 f f f 合并,则可以将 f f f 作为初状态。

因此:可以倍增求出 f f f 。新开背包数组 h h h 初始为空。从小到大枚举 i ∈ [ 1 , n ] i\in[1,\sqrt{n}] i[1,n ],做背包转移,同时将 f ( i ) f(i) f(i) “放入” 背包 h h h 。这就选择完了 ⩽ n \leqslant\sqrt{n} n 的数。然后从大到小枚举 i ⩽ n i\leqslant\sqrt{n} in 作为数字个数,同样地新开背包做转移,并每次将 h h h f ( j )    ( j > n ) f(j)\;(j>\sqrt{n}) f(j)(j>n ) 放入。

为了减小空间消耗,放入背包时 h ( j ) h(j) h(j) 应放至 ( j + i n ) (j{+}i\sqrt{n}) (j+in ) 位置,即 > n >\sqrt{n} >n 的限制强制满足。而 f ( j ) f(j) f(j) 应放至 ( j + i j ) (j{+}ij) (j+ij) 位置,因为它的限制是 > j >j >j

由此 T ( n ) = T ( n 2 ) + O ( n n ) = O ( n n ) T(n)=T(\frac{n}{2})+\mathcal O(n\sqrt{n})=\mathcal O(n\sqrt{n}) T(n)=T(2n)+O(nn )=O(nn )

最后吐槽: n ⩽ 5 × 1 0 5 n\leqslant 5\times 10^5 n5×105 就算 O ( n log ⁡ 2 n ) \mathcal O(n\log^2 n) O(nlog2n) 我都觉得磕碜,然而你告诉我是 O ( n 1.5 ) \mathcal O(n^{1.5}) O(n1.5) 的?

代码

#include <cstdio>
#include <algorithm> // Almighty XJX yyds!!!
#include <cstring> // oracle: ZXY yydBUS!!!
#include <cctype> // Huge Egg-Dog devours me
using llong = long long;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar()) if(c == '-') f = -f;
	for(; isdigit(c); c=getchar()) a = a*10+(c^48);
	return a*f;
}

const int MAXN = 1<<19;
int mod, f[MAXN], g[MAXN], tmp[MAXN];
inline void modAddUp(int &x, const int &y){
	if((x += y) >= mod) x -= mod;
}

int main(){
	int n = readint(); mod = readint();
	f[1] = 1; // valid states
	for(int m=3,v=1; true; m=m<<1|1){
		while((v+1)*(v+1) <= m) ++ v; // v = sqrt(m)
		memset(tmp+1,0,m<<2), tmp[1] = mod-1;
		rep(i,1,v){ // value (by column)
			drep(j,m,i) modAddUp(tmp[j],tmp[j-i]);
			modAddUp(tmp[i],f[i]); // into range
		}
		memset(g+1,0,m<<2); // clear
		drep(i,v,1){ // how many (by row)
			rep(j,1,m-i*v) modAddUp(g[j+i*v],tmp[j]);
			rep(j,v+1,m-i*j) modAddUp(g[j+i*j],f[j]);
			memmove(g+i,g,(m-i+1)<<2), memset(g,0,i<<2);
			rep(j,0,m-i) modAddUp(g[j+i],g[j]); // knapsack
		}
		rep(j, (m>>1)+1, m) modAddUp(
			f[j]=mod-tmp[j], mod-g[j]);
		if(m >= n) break; // enough
	}
	int ans = 0, v = 1;
	for(int i=n; i; --i,v=(v<<1)%mod)
		ans = int((ans+llong(mod-v)*f[i])%mod);
	modAddUp(ans,v); printf("%d\n",ans);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值