【luogu P8340】山河重整(容斥)(DP)

山河重整

题目链接:luogu P8340

题目大意

有 1~n 这 n 个数,问你有多少种选的方案,使得 1~n 中的每个数都可以表示为你选的其中一些数的和。

思路

考虑怎么才会满足条件,我们考虑从小的数开始组。
\(1\) 肯定要有 \(1\)\(2\) 的话也是,那我们看 \(3\) 以及以上 \(i\),那你会发现你肯定要用 \(\leq i\) 的来拼,那首先由必要条件是 \(\leq i\) 的部分的和要 \(\geq i\),然后其实你会发现它是必要的 。

因为你 \(1\sim i-1\) 的都可以被凑出来,找到最小的 \(x\) 使得 \(\leq x\) 的和 \(\geq i\),那设这个值是 \(t\),那 \(i\leq t< 2i\),那 \(t-i< i\),那这个也可以被表示出来。
那你用 \(t\) 的集合减去 \(t-i\) 的集合不就得到 \(i\) 了吗。


于是我们就变成数有多少个集合使得对于所有的 \(i\) 都满足集合中 \(\leq i\) 的值的和 \(\geq i\)

发现这个是每个位置都要满足不好统计,考虑找不合法的:存在一个位置 \(i\) 使得 \(\leq i\) 的值的和 \(<i\)
那接着就有一个比较显然的东西就是要满足这个一定会有 \(\leq i-1\) 的值恰好为 \(i-1\)
(然后接着 \(i\) 这个位置没有值)

那我们是不是可以根据这个来弄呢?
\(f_{i}\)\(\leq i\) 的部分选一些恰好为 \(i\),且前面过程全部满足合法条件的。
那这里又要合法条件了,那也没关系,我们再看怎么容斥掉嘛。
那先看不管合不合法的,先有多少种情况。
那就是一个整数划分(划分成的数互补相同),然后你会发现拆出来数的个数是 \(\sqrt{n}\) 级别的。
\(1+2+3+...+n=n*(n-1)/2\)

那我们考虑能不能从这里搞,弄出这个类似表格的东西:

这个表示的就是 \(4+3+1=8\) 的一个拆分。
那我们你这里是一列一列的加,我们能不能一行一行的加呢?毕竟行的长度是 \(\sqrt{n}\) 级别。
那其实是可以的,那我们看看有什么特别的性质:
会发现如果一行有 \(x\) 的长度,那一定要有 \(1\sim x-1\) 的长度。
然后同一个长度没有数量限制,那不就是一个有上面这个特别限制的完全背包吗!
然后至于这个限制怎么弄,我们就从大到小来枚举放行的长度,然后这样我们可以强制要么新开一个放,要么就一定要强制在之前的基础上继续放,这样就可以满足上面的条件了。
(具体实现可以看看代码,就是强制至少选一个,然后再完全背包)


那接下来就只剩下一个问题了,就是怎么减去不合法的方案。
那不难看出不合法其实就是你这里不满足的一部分,那感觉就可以 DP。

那我们再看看不合法具体会怎样:
首先一个位置 \(j\) 满足 \(\leq j\) 的和为 \(j\),然后 \(j+1\) 没有,然后后面 \(j+2\sim i\) 中会选一些跟 \(j\) 加起来得到 \(i\)
那这里就有一个条件,就是后面是一定要选的,那选最小的值就是 \(j+2\),那有可能能凑到 \(i\) 就至少需要 \(j+j+2\leq i\)

那后面就是 \(i-j\) 个里面从 \([j+2\sim i]\) 取得。
(后面也是拆)

拆像之前一样拆,然后那每个数多放进去就额外有 \(j+2\) 的贡献。
所以你每个位置初始放就是不一样的了:
那你得先枚举 \(j\),然后你重新看是什么代表着放进去数的数量,会发现其实就是 \(i\),那你初始化的位置就是 \(j+(j+2)*i\),那放的值就是 \(f_j\)
然后也是完全背包就可以。


然后又有一个问题,你求 \(f_i\) 需要用到 \(f_j\),也就是要前面的值啊。
那怎么搞呢?(其实会发现这个东西很分治)
然后你还发现还有一个性质就是我们刚刚说的 \(j+j+2\leq i\),那每次只会用到前面的一半当做 \(j\)
那我们根本不用分治,直接倍增就好,求出 \(1\sim 2^x\),然后用这个来求 \(2^x+1\sim 2^{x+1}\) 的答案即可。

代码

#include<cmath>
#include<cstdio>

using namespace std;

const int N = 5e5 + 10;
int n, mo, f[N], g[N], cf[N];

int jia(int x, int y) {return (x + y) >= mo ? x + y - mo : x + y;}
int jian(int x, int y) {return x < y ? x - y + mo : x - y;}
int cheng(int x, int y) {return 1ll * x * y % mo;}

void work(int n) {
	if (n <= 1) return ;
	work(n / 2);//倍增
	for (int i = 0; i <= n; i++) g[i] = 0;
	
	int B = sqrt(2 * n);
	for (int i = B; i >= 1; i--) { 
		for (int j = n; j >= i; j--) g[j] = g[j - i];
		for (int j = i - 1; j >= 0; j--) g[j] = 0;
		for (int j = 0; j + (j + 2) * i <= n; j++)//如果要选 i 那么 1~i 都要,所以是 i 个 
			g[j + (j + 2) * i] = jia(g[j + (j + 2) * i], f[j]);
		for (int j = i; j <= n; j++) g[j] = jia(g[j], g[j - i]);
	}
	
	for (int i = n / 2 + 1; i <= n; i++) (f[i] += mo - g[i]) %= mo;
}

void Init() {
	f[0] = 1; int B = sqrt(2 * n);
	for (int i = B; i >= 1; i--) {
		for (int j = n; j > i; j--) f[j] = f[j - i];//强制要选 i 
		for (int j = i; j >= 1; j--) f[j] = 0;
		for (int j = i; j <= n; j++)
			f[j] = jia(f[j], f[j - i]);//完全背包(继续选) 
	}
}

int main() {
	scanf("%d %d", &n, &mo);
	
	Init();
	work(n);
	
	cf[0] = 1; for (int i = 1; i <= n; i++) cf[i] = cheng(cf[i - 1], 2);
	int ans = cf[n];
	for (int i = 0; i < n; i++) ans = jian(ans, cheng(f[i], cf[n - i - 1]));
	printf("%d", ans);
	
	return 0;
} 
posted @ 2022-06-01 22:14  あおいSakura  阅读(25)  评论(0编辑  收藏  举报