Welcome to the NightCi|

XiaoLe_MC

园龄:1年2个月粉丝:3关注:9

2024-08-22 19:21阅读: 13评论: 0推荐: 0

[题解] permutation

[题解] Permutation

image

image

image

解析

一眼 DP 或者 组合。

70pts

场上推的DP

对于 (4,2,2),先把所有序列枚举出来:

1   21   31   42   32   43   4

可以发现,对于分割线上的部分,可以看作 (3,1,1) 的所有序列每个数 +1,然后前导一个 1。因为答案计算的是绝对值,所以每个数加一相当于整体抬高,不影响答案。而对于分割线下的部分,可以看作 (3,2,2) 的所有序列每个数 +1,同样不影响答案。令 f(i,j,z) 表示对应 (n,k,m) 的答案。那么有:

f(i,j,z)=f(i1,j1,z1)+f(i1,j,z)+?

留个问号是因为,这两部分合并还可以产生贡献,为上面部分最后一个数与下面部分第一个数的差的绝对值。上面部分最后一个数为 i,下面部分第一个数为 j+1。所以有:

f(i,j,z)=f(i1,j1,z1)+f(i1,j,z)+|ij1|

f(i,i,z)=0f(i,j,1)=ij

i,j,z 都是 106 级别的,空间会炸。可以发现 i 只和 i1 有关,可以滚掉。jz 同减,并且 zj,那么只维护最小的 z 即可。

场码
#include<bits/stdc++.h>
using namespace std;
constexpr int B = 1 << 23;
char buf[B], *p1 = buf, *p2 = buf;
#define gt() (p1==p2 && (p2=(p1=buf)+fread(buf, 1, B, stdin), p1==p2) ? EOF : *p1++)
template <typename T> inline void rd(T &x){
x = 0; int f = 0; char ch = gt();
for(; !isdigit(ch); ch = gt()) f ^= ch == '-';
for(; isdigit(ch); ch = gt()) x = (x<<1) + (x<<3) + (ch^48);
x = f ? -x : x;
}
char obuf[B], *O = obuf;
#define pt(ch) (O-obuf==B && (fwrite(obuf, 1, B, stdout), O=obuf), *O++=(ch))
template <typename T> inline void wt(T x){
if(x < 0) pt('-'), x = -x;
if(x >= 10) wt(x / 10); pt(x % 10 ^ 48);
}
#define fw fwrite(obuf, 1, O - obuf, stdout)
#define ll long long
#define ull unsigned long long
constexpr int N = 1e6 + 5, M = 1e9 + 7;
int n, k, m, f[2][N], lst, now = 1;
int main(){
// freopen("perm.in", "r", stdin);
// freopen("perm.out", "w", stdout);
rd(n), rd(k), rd(m);
if(n == k) wt(m);
else {
int tmp = k - m;
for(int i=tmp; i<=n; ++i){
for(int j=max(1, m+i-n); j+tmp<=i; ++j){
if(j == 1) f[now][j] = i - (j + tmp);
else if(i == j + tmp) f[now][j] = 0;
else f[now][j] = ((ll)f[lst][j] + (ll)f[lst][j-1] + abs(i - j - tmp - 1)) % M;
}
for(int j=max(i, m+i-1-n); j+tmp<=i-1; ++j) f[lst][j] = 0;
now ^= 1, lst ^= 1;
} wt(f[lst][m]);
} return fw, 0;
}

另一种 DP 思路

先打表找规律。打出 8 以内的表可以发现,答案只和 nmk 相关,那么把 k 设为横坐标,nm 设为纵坐标,于是有:

123451491625161736651827661351103910824711253164415

f(i,j) 表示图上对应横坐标与纵坐标的值。于是有:

f(i,j)=f(i1,j)+f(i,j1)+j

注意边界判断:

f(1,i)=i+1f(i,0)=0

答案即为:

f(k,nm1)

于是 O(n2) 递推即可。

100pts

考虑优化刚才的那个 DP,可以发现,对于点 (i,j) 的答案可以看作是从这个点一直走到到点 (0,0) 的所有路径的权值总和,每个点的权值即为这个点所在的纵列编号(纵坐标)。这个东西可以用组合求解。可以枚举每一个点 (i,j),一共有 (i+ji) 条路径经过这一点。对于边界 i=1 需要特判一下,看作权值为 1。于是可以列出式子:

i=0k2j=0n(nj)(i+jj)+j=0n(k1+jj)

但是这个式子是 O(n2) 的。所以考虑优化。有一个公式:

i=mn(a+ii)=(a+n+1n)(a+mm1)

用上面的式子套公式即可优化到一维:

j=0n(nj)(j+k2j+1)+j=0n(k1+jj)

j=0n(nj)(j+k2j+1)+(k+nn)

复杂度 O(n)

code
#include<bits/stdc++.h>
using namespace std;
constexpr int N = 1e6 + 5, M = 1e9 + 7;
int n, m, k, ans, p[N<<1], inv[N<<1];
#define ll long long
inline int qpow(int a, int k){
int as = 1;
while(k){
if(k & 1) as = (ll)as * a % M;
a = (ll)a * a % M; k >>= 1;
} return as;
}
inline int C(int a, int b){
return (ll)p[a] * inv[b] % M * inv[a-b] % M;
}
int main(){
freopen("perm.in", "r", stdin);
freopen("perm.out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>m>>k;
if(n == m) return cout<<m, 0;
n -= m + 1;
p[0] = 1; for(int i=1; i<=n+m; ++i) p[i] = (ll)p[i-1] * i % M;
inv[n+m] = qpow(p[n+m], M-2); for(int i=n+m-1; i>=0; --i) inv[i] = (ll)inv[i+1] * (i+1) % M;
if(k >= 2) for(int j=0; j<=n; ++j) ans = ((ll)ans + (ll)(n-j) * C(j+k-1, j+1) % M) % M;
for(int j=0; j<=n; ++j) ans = ((ll)ans + (ll)C(k+j-1, j)) % M;
return cout<<ans, 0;
}

本文作者:XiaoLe_MC

本文链接:https://www.cnblogs.com/xiaolemc/p/18374557

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   XiaoLe_MC  阅读(13)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起