【P7967 [COCI2021-2022#2] Magneti】题解
PS:这题做得真是一波三折。
为了方便
考虑现在固定磁铁们的一个排列,使得第
那么这样的排列至少要占据
接下来称某一磁铁排列的
算法一、状压dp
用状压
设
则有
总时间复杂度为
算法二、容斥优化dp
状压
一种优化思路是想办法把这个限制去掉,也就是把集合这一信息去掉,但是这样
具体做法是,现在我们只使用集合
于是在只使用集合
对于单个
利用容斥原理则有
然而我们仍然需要以
这个技巧的来源:P3349 [ZJOI2016]小星星。
算法三、从插入的角度处理排列
首先考虑
因为是将元素以一定顺序依次插入,所以不需要已使用元素的集合信息,也不需要容斥。
于是不妨试试从插入的角度看看有什么突破口,插入的顺序一般有从小到大,也有从大到小,根据这题的性质,半径大的磁铁的
设
假设现在的排列已经有了
如果选择插入在两侧,则磁铁排列的直径的增加量一定是
然而在选择插入到
这就代表这个技巧一定失败了吗?未必,在哪遇到什么困难就努力解决什么困难,我们想办法设计一个不会引起磁铁排列直径减少的
设
考虑在插入第
(1)从
(2)第
(3)从
其中乘
于是转移方程可以写成
边界条件为
则有
总时间复杂度为
这个技巧的来源:P5825 排列计数。
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
#define re register
#define sf scanf
#define pf printf
#define nl() putchar('\n')
#define ms(x, val) memset(x, val, sizeof(x))
#define ll long long
#define _for(i, a, b) for(re int (i) = (a); (i) < (b); ++(i))
#define _rfor(i, a, b) for(re int (i) = (a); (i) <= (b); ++(i))
#define maxn 53
#define maxm 10005
#define mod 1000000007ll
int iv[maxm], ivf[maxm], fct[maxm], f[2][maxn][maxm], r[maxm];
int C(re int n, re int m){ return (ll)fct[n]*ivf[m]%mod*ivf[n-m]%mod; }
void init(re int n){
ms(f, 0); f[0][1][0] = 1;
iv[1] = fct[0] = fct[1] = ivf[0] = ivf[1] = 1;
_rfor(i, 2, n){
iv[i] = (ll)iv[mod%i]*(mod-mod/i)%mod;
fct[i] = (ll)fct[i-1]*i%mod;
ivf[i] = (ll)ivf[i-1]*iv[i]%mod;
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("sample.in", "r", stdin);
//freopen("sample.out", "w", stdout);
#endif
re int n, l;
sf("%d %d", &n, &l);
_rfor(i, 1, n) sf("%d", r[i]), --r[i];
sort(r+1, r+n+1);
init(l);
re int cur = 0;
_rfor(i, 2, n){
_rfor(j, 1, i) _rfor(k, 0, l-n){
f[cur^1][j][k] = (
(j <= i-1 && k >= r[i] ? (ll)2*j*f[cur][j][k-r[i]] : 0ll) +
(j > 1 ? f[cur][j-1][k] : 0ll) +
(j+1 <= i-1 && k >= 2*r[i] ? (ll)(j+1)*j*f[cur][j+1][k-2*r[i]] : 0ll)
)%mod;
}
cur ^= 1;
}
re int ans = 0;
_rfor(k, 0, l-n) if (f[cur][1][k])
ans = (ans + (ll)C(l-k, n)*f[cur][1][k])%mod;
pf("%d\n", ans);
return 0;
}