【清华2019冬令营模拟12.15】排列

Description:

在这里插入图片描述
n<=5000

题解:

考虑对一个值i它的区间的长度是多少。

区间可以只考虑右半部分的,左边的倒过来做一遍就行了。

若i是固定的,找到右边第一个固定的j>i

那么区间肯定不能超过j所在的位置。

可以直接枚举区间的右端点,这个右端点选的大于i,它左边的小于i,剩余的乱排,用排列数算即可。

这一部分O(n2)O(n^2)O(n2)

若i不是固定的,找到整个序列固定的大于i的,它们把整个序列分成若干段,对于每一段分开做。

考虑枚举相邻j,k(j<k)的为固定点,它们这一段要被统计,则(i,k]里的不固定点都要小于i,那么维护
s[x]=s[x−1]∗P(L,x)∗(S−x−1)!s[x]=s[x-1]*P(L,x)*(S-x-1)!s[x]=s[x1]P(L,x)(Sx1)!
L表示小于i的不定的个数 ,S表示总不定的个数,这样就对一组(j,k)就可以快速查询了。

Code:

#include<cstdio> 
#include<algorithm>
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
using namespace std;

const int mo = 998244353;

ll ksm(ll x, ll y) {
	ll s = 1;
	for(; y; y /= 2, x = x * x % mo)
		if(y & 1) s = s * x % mo;
	return s;
}

const int N = 5005;

ll fac[N], nf[N];
int n, a[N];
ll ans, s[N];
int S, L, bz[N], d[N], d0, p[N], p0;

void gg() {
	S = 0; fo(i, 1, n) S += !a[i];
	fo(i, 1, n) bz[i] = 0;
	fo(i, 1, n) bz[a[i]] = i;
	L = 0; a[n + 1] = n + 1;
	fo(i, 1, n) {
		if(bz[i]) {
			d0 = 0;
			fo(j, bz[i] + 1, n + 1) {
				if(a[j] > i) {
					d[++ d0] = j; break;
				}
				if(!a[j]) d[++ d0] = j;
			}
			ans += fac[S] * (d[1] - bz[i]) % mo;
			fo(j, 1, d0 - 1) if(L - j >= 0 && S - j >= 0)
				ans += fac[L] * nf[L - j] % mo * fac[S - j] % mo * (d[j + 1] - d[j]) % mo;
			ans %= mo;
		} else {
			s[0] = fac[S - 1];
			fo(j, 1, L) s[j] = (s[j - 1] + fac[L] * nf[L - j] % mo * fac[S - j - 1]) % mo;
			fo(j, L + 1, n) s[j] = s[L];
			a[0] = n + 1; d0 = 0;
			fo(j, 0, n + 1) if(a[j] > i) d[++ d0] = j;
			fo(j, 1, d0 - 1) {
				p0 = 0;
				fo(k, d[j], d[j + 1]) if(!a[k]) p[++ p0] = k;
				p[++ p0] = d[j + 1];
				fo(i, 1, p0 - 1) ans += (p[i + 1] - p[i]) * s[i - 1] % mo;
				ans %= mo;
			}
			L ++;			
		}
	}
}

int main() {
	freopen("arrange.in", "r", stdin);
	freopen("arrange.out", "w", stdout);
	scanf("%d", &n);
	fac[0] = 1; fo(i, 1, n) fac[i] = fac[i - 1] * i % mo;
	nf[n] = ksm(fac[n], mo - 2);
	fd(i, n, 1) nf[i - 1] = nf[i] * i % mo;
	fo(i, 1, n) scanf("%d", &a[i]);
	gg();
	fo(i, 1, n / 2) swap(a[i], a[n - i + 1]);
	gg();
	ans = ((ans - fac[S] * n) % mo + mo) % mo;
	printf("%lld", ans * nf[S] % mo);
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值