[2017 山东一轮集训 Day7]逆序对
壹、题目描述 ¶
贰、题解 ¶
本来有一种十分完美的生成函数解法。
我们不难发现,最后的多项式实际上就是:
我们可以将分子全部拿出来看看:
这不禁让人想起了付公主的背包,使用类似的方法进行化简:
求 \(\exp\) 里面的东西是 \(\mathcal O(k\ln k)\) 的,做 \(\exp\) 是 \(\mathcal O(k\log k)\) 的,最后的复杂度就是 \(\mathcal O(k\log k+k\ln k)\).
注意别忘记了还有一个 \(1\over (1-x)^n\).
但是由于 \(\bmod=10^9+7\),除非你打三模 \(\rm NTT\) 或者 \(\rm FFT\) 或者 \(\rm MTT\).
还有另一种做法,对最本质的问题使用容斥 —— 钦定一些 \(i\) 使得 \(D(x)\ge x\). 假定他们的和为 \(s\),通过隔板法,不难发现分配剩下的数的方案数就是 \({k-s+n-1\choose n-1}\).
那么,现在问题是,如何从 \(1,2,3,\cdots n\) 中选择 \(i\) 个数使得他们的和刚好为 \(s\) ?这里有一个很妙的 \(\rm DP\):
设 \(f(i,j)\) 表示当前有 \(i\) 个数,和为 \(j\).
考虑转移:
- 若 \(j\ge i\),那么我们可以考虑:
- 将当前所有数都加上 \(1\),方案数加上 \(f(i,j-i)\);
- 第 \(i\) 个数是刚刚插入的,将前 \(i-1\) 个数加 \(1\) 之后在末尾放上一个 \(1\),方案数加上 \(f(i-1,j-i)\);
- 若 \(j>n\),那么可能出现最大的数变成 \(n+1\) 的情况,所以考虑将这种情况减掉,减去 \(f(i-1,j-n-1)\);
该转移妙在,我们维护的是一个动态的过程,可以在序列中加数,但是在加数之前,要先将前面的所有数平移一位,以保证所有的数互不相同。
其实也可以从差分数组角度理解吧?
以上 \(\rm DP\) 是 \(\mathcal O(k\sqrt k)\) 的,总复杂度就是这个咯。
艹,傻 [哔] 出题人,不给 \(\rm NTT\) 模数,出题人我 [哔] 你 [哔] 。
另,计算答案时不要忘记系数。
叁、参考代码 ¶
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<vector>
using namespace std;
// #define NDEBUG
#include<cassert>
namespace Elaina{
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define mmset(a, b) memset(a, b, sizeof a)
// #define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
}
using namespace Elaina;
const int mod=1e9+7;
const int maxk=2e5;
const int sqrtk=450;
inline int qkpow(int a, int n){
int ret=1;
for(; n>0; n>>=1, a=1ll*a*a%mod)
if(n&1) ret=1ll*ret*a%mod;
return ret;
}
int fac[maxk+5], finv[maxk+5];
inline void prelude(){
fac[0]=finv[0]=1;
for(int i=1; i<=maxk; ++i)
fac[i]=1ll*fac[i-1]*i%mod;
finv[maxk]=qkpow(fac[maxk], mod-2);
for(int i=maxk-1; i>=1; --i)
finv[i]=1ll*finv[i+1]*(i+1)%mod;
}
inline int C(int n, int m){
if(n<m) return 0;
return 1ll*fac[n]*finv[m]%mod*finv[n-m]%mod;
}
int n, k, m=450;
int f[sqrtk+5][maxk+5];
inline void getf(){
f[0][0]=1;
for(int i=1; i<=m; ++i){
for(int j=i; j<=k; ++j){
if(j>=i) f[i][j]=(0ll+f[i][j-i]+f[i-1][j-i])%mod;
if(j>n) f[i][j]=(0ll+f[i][j]+mod-f[i-1][j-n-1])%mod;
}
}
}
#define sign(i) (((i)&1)? -1: 1)
inline void getans(){
int ans=0;
for(int s=0; s<=k; ++s){
int cnt=0;
for(int j=0; j<=m; ++j)
cnt=(0ll+cnt+mod+sign(j)*f[j][s])%mod;
ans=(0ll+ans+mod+1ll*C(k-s+n-1, n-1)*cnt%mod)%mod;
}
writc(ans);
}
signed main(){
prelude();
n=readin(1), k=readin(1);
getf();
getans();
return 0;
}
肆、关键之处 ¶
不得不说,这种转移真的很巧妙,转移一个 “动态” 的数组,操作有二:
- 将数组中每个元素加一;
- 将数组中每个元素加一之后,在末尾放入一个 \(1\);
第一个操作是为了处理数中间有“断层”的情况,第二个操作是为了加入元素,并且保证元素两两不同。