JZOJ 6809. 【2020.10.29提高组模拟】不难题(容斥+DP)

JZOJ 6809. 【2020.10.29提高组模拟】不难题

题目大意

  • K K K 1 − N 1-N 1N的排列,每次可以挑选一个队列取出队首,但不能连续取出 K K K个相同的数,要求取出每个区间 [ l , r ] [l,r] [l,r]中排列且不能连续取出 r − l + 1 r-l+1 rl+1个相同的数的方案数。
  • N , K ≤ 300 N,K\leq 300 N,K300

题解

  • 这题可以联想到平面上只能向右向上走,要求到达某个点且有若干个点不能经过的方案数,
  • 可以用容斥来做设 f i f_i fi表示仅仅经过了第 i i i个不能经过的点的方案数,用总方案数减去其他 f j f_j fj即为 f i f_i fi,简单组合数计算即可。
  • 这题也是同理,但维度变成了 K K K维,同样设 f i f_i fi表示仅有 i i i连续出现了若干次,那么可以枚举 f j f_j fj转移到 f i f_i fi,当然要注意这里不只是组合数,而是先要到达每个 i i i前一个的位置,然后再乘阶乘,暴力做是 O ( N 2 K 3 ) O(N^2K^3) O(N2K3)的,中间计算简单优化一下可以达到 O ( N 2 K 2 ) O(N^2K^2) O(N2K2),仍旧是过不了。
  • 发现题目规定了是随机的,也就是区间跨度越大,可以转移的量就会越少,那么直接暴力把每次可以转移的记录下来,就可以过了。
  • 注意区间右端点最好要从左端点 + 1 +1 +1开始枚举,不然常数大可能过不了,另外可以尽量减少模运算的次数。

代码

#include<cstdio>
#include<queue>
using namespace std;
#define N 310
#define ll long long
#define md 1000000007
int a[N][N], p[N][N];
ll F[N * N], G[N * N], f[N];
int Sum[N][N], S[N][N], sum[N], s[N];
queue<int> q[N];
ll C(int x, int y) {
    
    
	return F[x] * G[y] % md * G[x - y] % md;
}
ll ksm(ll x, ll y) {
    
    
	if(!y) return 1;
	ll l = ksm(x, y / 2);
	if(y % 2) return l * l % md * x % md;
	return l * l % md;
}
int main() {
    
    
	int n, m, i, j, k, l, h, x;
	scanf("%d%d", &m, &n);
	F[0] = 1;
	for(i = 1; i < N * N; i++) F[i] = F[i - 1] * i % md;
	G[N * N - 1] = ksm(F[N * N - 1], md - 2);
	for(i = N * N - 2; i >= 0; i--) G[i] = G[i + 1] * (i + 1) % md;
	for(i = 1; i <= m; i++) {
    
    
		for(j = 1; j <= n; j++) {
    
    
			scanf("%d", &a[i][j]);
			p[i][a[i][j]] = j;
		}
		a[i][n + 1] = p[i][n + 1] = n + 1;
	}
	ll ans = 0, tot;
	for(i = 1; i < m; i++) {
    
    
		for(k = 1; k <= n + 1; k++) sum[k] = p[i][k] - 1, s[k] = 1;
		for(k = 1; k <= n + 1; k++)
			for(l = 1; l < k; l++) if(p[i + 1][a[i][l]] < p[i + 1][a[i][k]]) q[a[i][k]].push(a[i][l]), Sum[a[i][k]][a[i][l]] = k - l - 1, S[a[i][k]][a[i][l]] = 1;
		for(j = i + 1; j <= m; j++) {
    
    
			for(h = 1; h <= n + 1; h++) {
    
    
				k = a[i][h];
				s[k] = s[k] * C(sum[k] + p[j][k] - 1, sum[k]) % md;
				sum[k] += p[j][k] - 1;
				f[k] = s[k] * F[j - i + 1] % md;
				x = q[k].size(), tot = 0;
				while(x--) {
    
    
					l = q[k].front();
					q[k].pop();
					S[k][l] = S[k][l] * C(Sum[k][l] + p[j][k] - p[j][l] - 1, Sum[k][l]) % md;
					Sum[k][l] += p[j][k] - p[j][l] - 1;
					tot += f[l] * S[k][l] % md;
					if(p[j + 1][l] < p[j + 1][k]) q[k].push(l);
				}
				f[k] = (f[k] - tot % md * F[j - i + 1] % md + md) % md;
			}
			ans += f[n + 1] * G[j - i + 1] % md;
		}
	}
	printf("%lld\n", ans % md);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39565901/article/details/109426597