[FJWC2018]全排列 DP
题面
题解
(表示第一段文字导致我在考场上没看懂题……因为我以为这个定义是定义在整个排列上的,所以相似 = 相同。结果其实是可以应用在一个区间上……)
首先我们发现,2个区间相似,其实就是离散化之后相同。
观察到,相似区间的位置是没有影响的,且由于数字两两不同,所以不管相似区间内是哪些数,我们都可以将其当做一个1 ~ len的排列来计算。
因此我们可以直接枚举相似区间的长度和逆序对个数,然后按照从小到大的顺序加入下一个数,DP出方案,也就是区间个数。
设f[i][j]表示长度为i,逆序对恰好j个方案数。
因为新插入的i严格大于1 ~ i - 1,所以插入x带来的新逆序对个数为0 ~ i - 1,因此有:
\[f[i][j] = \sum_{k = j - i + 1}^{j} f[i - 1][k]
\]
因此用前缀和优化一下就可以同时做到快速转移 + 逆序对至多j。
然后对于每次询问,我们枚举一下区间长度,用组合数和阶乘算一下区间放不同数字和区间放在不同位置的方案数,求和输出即可。
注意一个坑点,排列有2个,所以组合数和阶乘都要乘2次……
#include<bits/stdc++.h>
using namespace std;
#define R register int
#define LL long long
#define AC 510
#define ac 130100
#define p 1000000007
#define maxn 500
int T, n, m, ans;
int f[AC][ac], C[AC][AC], fac[AC];
inline int read()
{
int x = 0;char c = getchar();
while(c > '9' || c < '0') c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x;
}
inline void upmin(int &a, int b) {if(b < a) a = b;}
inline void upmax(int &a, int b) {if(b > a) a = b;}
inline void up(int &a, int b) {a += b; if(a >= p) a -= p;}
inline int ad(int a, int b) {return ((a + b) % p + p) % p;}
inline int mul(int a, int b) {return 1LL * a * b % p;}
inline int two(int x) {return 1LL * x * x % p;}//因为有2个排列,所以组合数和阶乘都要乘2次
void get()
{
for(R i = 0; i <= maxn; i ++) C[i][0] = 1;
for(R i = 1; i <= maxn; i ++)
for(R j = 1; j <= maxn; j ++)
up(C[i][j], C[i - 1][j - 1]), up(C[i][j], C[i - 1][j]);
fac[0] = fac[1] = 1;
for(R i = 2; i <= maxn; i ++) fac[i] = mul(fac[i - 1], i);
}
void build()
{
for(R i = 0; i <= maxn; i ++) f[i][0] = 1;//初始化
for(R i = 1; i <= maxn; i ++)//枚举数的个数
{//f[i][j] = \sum_{k = j - i + 1}^{j}{f[i - 1][k]}相当于恰好转移到恰好,而后面的sum部分就是至多,我们所需要的也是至多
int all = (i - 1) * i / 2, tmp = maxn * maxn / 2;//所以转移的时候顺便求一下前缀和就好了
for(R j = 1; j <= tmp; j ++)//枚举逆序对的个数,因为是至多j个,所以>all的也要枚举
{//因为插入第i个数最多产生i - 1的贡献,所以还需要减去一些东西(k <= j - i就不合法了,凑不到i个逆序对)
if(j <= all) f[i][j] = ad(f[i - 1][j], (j - i >= 0) ? - f[i - 1][j - i] : 0);
up(f[i][j], f[i][j - 1]);
}
}
}
void work()
{
T = read();
while(T --)
{
n = read(), m = read(), ans = 0, m = min(n * n / 2, m);//对这个取min!因为前面的DP数组只统计到这了
for(R i = 1; i <= n; i ++)
up(ans, mul(mul(mul(f[i][m], two(C[n][i])), two(fac[n - i])), (n - i + 1)));
printf("%d\n", (ans + p) % p);
}
}
int main()
{
// freopen("in.in", "r", stdin);
get();
build();
work();
// fclose(stdin);
return 0;
}