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

山河重整

题目链接:luogu P8340

题目大意

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

思路

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

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


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

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

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

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

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


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

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

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

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


然后又有一个问题,你求 fi 需要用到 fj,也就是要前面的值啊。
那怎么搞呢?(其实会发现这个东西很分治)
然后你还发现还有一个性质就是我们刚刚说的 j+j+2i,那每次只会用到前面的一半当做 j
那我们根本不用分治,直接倍增就好,求出 12x,然后用这个来求 2x+12x+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; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P8340.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示