题目描述
有一个大小为 \(n\) 的数可重集 \(w\),你需要将其分成 \(k\) 组,每组至少有一个数。对于一个大小为 \(s\),包含数字之和为 \(t\) 的分组,它的权值是 \(s\times t\)。
每种分组方案的权值就是每个分组的总和。请求出所有分组方案的权值和取模 \(998244353\) 下的结果。
\(n\le 10^6\)。
解法
就觉得... 自己挺傻的。以下是自己的考场思路:
枚举长度 \(i\),计算任意能源在长度 \(i\) 中的方案数 —— \(n-i\) 个能源分成 \(k-1\) 组方案数 × 从 \(n-1\) 中选 \(i-1\) 个 × 所有能量值之和 × \(i\)。
好耶ヽ(✿゚▽゚)ノ把样例凑出来了!
考虑将 \(n\) 个元素分成 \(m\) 组。若知晓了每组的大小 \(\{s\},\sum s=n\),此时就是 \(\frac{n!}{s_1!s_2!...s_m!}\)。
令 \(dp_{i,j,k}\) 为 \(i\) 个元素分成 \(j\) 组,最后一组长度为 \(k\) 的方案数,而且保证长度递增。因为我不会去重。
哦嚯,后面就是个组合数,最重要的是和 \(q\) 无关。所以搞个前缀和?希望不要假了。只有 \(\text{40 pts}\)?怎么旁边都在说卷积?日你吗,这个不是 \(\mathcal{O}(n^3)\) 的吗。
但是其实是不是除以 \(j!\) 就可以去重了?可是状态数就已经死了。
这个柿子可以卷积?每个式子变化量不同,咋维护啊?可是写 \(\mathcal{O}(n^3)\) 不就和指数级暴力同分了吗?
出题人谢谢你????。
考完之后身边的 \(\rm ET\) 就对我说将 \(n\) 个互异元素分成 \(m\) 组就是第二类斯特林数!递推也非常简单可以做到 \(\mathcal O(n^2)\):
心情真的坏透了,大家都会容斥。
事实上,第二类斯特林数可以用容斥原理来计算,先给出结果:
我们不妨先钦定盒子是互异的,最后除以阶乘即可。令 \(A_i\) 为 \(n\) 个互异球放进 \(k\) 个盒子,第 \(i\) 个盒子为空的方案集合,那么我们需要求的就是 \(|\bigcap_{i=1}^k A_i^c|\)(\(c\) 符号表示补集)。
这样就可以祭出容斥原理:
用 \(f(t)\) 表示 \(\sum_{1\le i_1<i_2<...<i_t\le n}|A_{i_1}\cap ...\cap A_{i_t}|\)。注意到,\(A_i\) 的定义并没有保证其它盒子不为空,所以钦定一个下标集时,答案就是 \((k-t)^n\)。钦定下标集是 \(\binom{k}{t}\) 的。左边就是 \(\text{S}(n,k)\cdot k!\),所以原式得证。
不过按我的做法还是需要计算 \(n\) 个斯特林数,复杂度并没有优化。
有一个思路上特别妙的转化:我们并不一定需要知道一个数处在分组的大小,当数字 \(x\) 和另一个数字 \(y\) 处于一个分组时,分组权值就会增加 \(x+y\)!所以答案还可以这样描述(就是将两个数字捆绑在一起了):
对于 \((k-i)^n\) 用欧拉筛一下就可以做到 \(\mathcal{O}(n)\) 了。
七负我题目描述
青山七海是打工狂魔。
她计划拿出 \(x\) 小时进行打工,共有 \(n\) 家店铺可以去,她需要将这 \(x\) 小时的打工时间分配到这些店铺。
这 \(n\) 家店铺的基础工资是一样的,但青山七海了解到,它们之间有 \(m\) 对合作伙伴关系。若店铺 \(u, v\) 是合作伙伴,并且青山七海在 \(u, v\) 的打工时间分别为 \(t_u, t_v\),就能额外获得 \(t_u ×t_v\) 的工资。
青山七海想知道,在恰当分配这些打工时间的情况下,她最多能获得多少额外工资呢?
一个店铺被分配到的打工时间可以为任意非负实数,即包括 \(0\)。
\(1\le n\le 40,1\le x\le 100\)。
解法
若 \(u,v\) 之间没有边,记 \(s_u,s_v\) 分别表示与 \(u,v\) 相连的点的 \(t\) 之和。那么 \(u,v\) 对答案的贡献就是 \((s_u\cdot t_u+s_v\cdot t_v)\),当 \(t_u+t_v\) 为定值 \(S\) 时,这个柿子就是 \((s_u-s_v)\cdot t_u+s_v\cdot S\)。你会发现当 \(s_u-s_v>0\) 时,柿子在 \(t_u=S\) 时取最大值;反之,在 \(t_u=0\) 时取最大值 —— 也就是说,最优情况一定是 \(t_u,t_v\) 中有一个为零。
那么最优解一定是 \(t>0\) 的点形成一个团(团内部的点两两有边)。
考虑贡献的形式是两两匹配,其实就是 \((t_1+t_2+...+t_k)^2\) 中的所有交叉项之和,也可以被表示成 \(\frac{\left(\sum_{i=1}^k t_i \right)^2-\sum_{i=1}^k t_i^2}{2}\)。前面一坨就是 \(x^2\),后面一坨取最小值时就是令 \(t_i=\frac{x}{k}\)。所以我们得出了已知团的大小得到最大答案的构造方案。
假设团大小为 \(k\),答案就是 \(\frac{k(k-1)\cdot x^2}{2k^2}\)。显然地,当 \(k\) 越大值就越大,所以只需要找到图上的最大团即可。
具体有两种方法:
\(\text{Meet in the middle}\)
将点分成两半。对于前一半求出 \(f(S)\) 表示点集为 \(S\) 的导出子图的最大团;对于后一半,枚举点集 \(P\),找到 \(N(P)\) 与前一半点的交集 \(V\),答案就是 \(|P|+f(V)\)。时间复杂度 \(\mathcal O(n\cdot 2^{n/2})\)。
\(\text{Bron–Kerbosch}\) 算法
时间复杂度是 \(\mathcal O(3^{n/3})\) 的,但我怎么可能会证明呢?
代码
/*
cnt_i:用 [i,n] 之间的点能构成的最大团。用于可行性剪枝。
now:现在尝试拓展第 now 个点
*/
bool dfs(int x,int now) {
for(int i=x+1;i<=n;++i) {
if(cnt[i]+now-1<=ans) return 0;
if(!e[x][i]) continue;
for(int j=1;j<=now;++j)
if(j==now) {
a[now]=i;
if(dfs(i,now+1))
return 1;
}
else if(!e[i][a[j]]) break;
}
if(now-1>ans) return ans=now-1,1;
return 0;
}
for(int i=n;i>=1;--i) {
a[1]=i;
dfs(i,2);
cnt[i]=ans;
}