jzoj3505NOIP2013模拟11.4A组组合逆元积木

Posted SSL_ZZL

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jzoj3505NOIP2013模拟11.4A组组合逆元积木相关的知识,希望对你有一定的参考价值。

题面

Description

小A正在搭积木。有N个位置可以让小A使用,初始高度都为0。小A每次搭积木的时候,都会选定一个拥有相同高度的区间[A…B],然后将位置[A+1…B-1]上的所有积木的高度加一。不幸的是,小A把积木搭好之后没多久,小A调皮的弟弟就将其中若干个位置上的积木弄倒了。小A想知道他原来的积木是如何摆放的,所以他求助于你,请你告诉他原来有多少种可能的摆法。
在这里插入图片描述

Input

第一行为一个正整数N,表示小A有N个位置。
第二行有N个由空格分隔的整数Hi,表示第i个位置的积木高度。-1表示这个位置上的积木已经被弄倒了。

Output

唯一的一行,输出包括可能的摆法mod 1,000,000,007的结果。

Sample Input

输入1:

3
-1 2 -1

输入2:

3
-1 -1 -1

输入3:

6
-1 -1 -1 2 -1 -1

Sample Output

输出1:

0

输出2:

2

输出3:

3

Data Constraint

对于50%的数据 1<=N<=1000 -1<=Hi<=1000
对于80%的数据 1<=N<=10000
对于100%的数据 1<=N<=20000 -1<=Hi<=10000
Source / Author: brick by 广州二中


解题思路

看到一堆人DP水过去了巨心动
f i , j f_{i,j} fi,j为到i位置,高度为j的方案数,加上一堆奇奇怪怪的操作
结果搞了一上午没搞出来,笑着笑着就哭了((((((
于是被迫搞正解((((((

对答案数有贡献的一定都是-1,那么就可以把-1段的答案求出来然后乘起来
(图画的有点大了((((((()
在这里插入图片描述

从x走到y,有三种方向可选——上(u)、下(d)、平移(r),枚举u,那么d = high[x] + u - high[y](上升和下降最后一定要在y的高度,不然就不可能形成到y的路线),u和d都知道了,r = y - x - u - d

考虑方案,现在u、d、r的数量都已知,那就是个组合数学的问题了
C y − x u C_{y - x}^{u} Cyxu是u的方案数, C y − x − u d C_{y - x - u}^{d} Cyxud是d的方案数,前两个已知,r方案数就只能是1了
C y − x u ⋅ C y − x − u d ⋅ 1 C_{y - x}^{u}·C_{y - x - u}^{d}·1 CyxuCyxud1

但这其中还有不合法的方案数,路线走到0下了(积木总不能叠到小于0吧(doge))
考虑求出不合法的方案数,用 总方案数 - 不合法方案数 = 答案
作y’是y对于-1线的对称点
x到y’的路线方案数一定是不合法的方案数

  1. 一定经过-1=》一定不合法
  2. 将 -1线下的路线 折叠到-1线上,发现一定能到y点=》一定在总方案数中
    在这里插入图片描述

此时出现大问题???(没错又是数论的锅)
C n m % P = A n m A m m % P = n ! m ! ⋅ ( n − m ) ! % P C_n^m \\% P = \\frac{A_n^m}{A_m^m} \\% P = \\frac{n!}{m!·(n-m)!} \\% P Cnm%P=AmmAnm%P=m!(nm)!n!%P
如果直接除直接炸飞天,于是需要用逆元((((((
数论补充站
快速幂求逆元模板
话说之前讲座学的是线性求,但是没打过((((((


Code

95pts

就是一直在卡啊TOT

#include <iostream>
#include <cstring>
#include <cstdio>
#define ll unsigned long long

using namespace std;

const int maxn = 20100;
const ll P = 1000000007;
int n, a[maxn], sta;
ll f[2][maxn];

int main() {
	freopen("brick.in", "r", stdin);
	freopen("brick.out", "w", stdout);
	scanf("%d", &n);
	for(int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	if(a[n] > 0) {
		printf("0");
		return 0;
	}
	f[1][0] = 1, sta = 1;
	for(int i = 2; i <= n; i++) {
		int now = i % 2, be = (i + 1) % 2;  //滚动数组,卡空间
		if(i <= n / 2 + 1) sta++;  //算出高度,卡内存
			else sta--;
		for(int j = 0; j <= sta + 1; j++) f[now][j] = 0;
		
		if(a[i] > -1) {
			f[now][a[i]] = f[be][a[i] - 1] + f[be][a[i]] + f[be][a[i] + 1];  //已有积木高度,高度固定
			if(i % 20 == 0) f[now][a[i]] %= P;  //每20次%P,卡时间
			continue;
		}
		for(int j = 0; j <= sta; j++) {
			f[now][j] = f[be][j - 1] + f[be][j] + f[be][j + 1];  //上、下、平移
			if(i % 20 == 0) f[now][j] %= P;
		}
	}
	printf("%lld", f[n % 2][0] % P);
} 

100pts

#include <iostream>
#include <cstdio>
#include <cmath>
#define ll long long

using namespace std;

const ll P = 1000000007;
ll n, a[20100], x, y, d, u, r, f[20100], answer = 1;

ll ny(ll a, ll k) {  //求逆元
	ll ans = 1;
	a %= P;
	while(k) {
		if(k & 1)ans = ans * a % P;
		k >>= 1;
		a = a * a % P;
	}
	return ans;
}

ll C(ll m, ll n) {  //求解C
	if(m == 0) return 1;
	return (f[n] % P * ny(f[m] * f[n - m] % P, P - 2) % P);
}

int main() {
//	freopen("brick.in", "r", stdin);
//	freopen("brick.out", "w", stdout);
	scanf("%lld", &n);
	f[1] = f[0] = 1;
	for(int i = 2; i <= n; i++) f[i] = f[i - 1] * (ll)i % P;  //预处理阶乘
	for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
	if(a[1] > 0 || a[n] > 0) {
		printf("0");
		return 0;
	}
	a[1] = a[n] = 0;
	for(int i = 1; i < n; i++)
		if(a[i] != -1 && a[i + 1] != -1 && abs(a[i] - a[i + 1]) > 1) {
			printf("0");
			return 0;
		}
	//************************************************************
	//以上均为预处理、判不合法等
	//************************************************************
	x = 1;
	for(int i = 2; i <= n; i++) {
		if(a[i] == -1) continue;
		y = i;
		ll ans = 0;
		for(int u = 0; u <= y - x; u++) {
			d = a[x] + u - a[y], r = y - x - u - d;
			if(d < 0 || r < 0) continue;
			ans = (ans + C(u, u + d + r) * C(d, d + r) % P) % P;  //合法方案数
		}
		ll ans_ = 0, ls = a[y];
		a[y] = -a[y] - 2;
		for(int u = 0; u <= y - x; u++) {
			d = a[x] + u - a[y], r = y - x - u - d;
			if(d < 0 || r < 0) continue;
			ans_ = (ans_ + C(u, y - x) * C(d, y - x - u) % P) % P;  //不合法方案数
		}
		a[y] = ls, x = y;
		answer = answer * (ans - ans_ + P) % P;  //不知道为什么要加P,但fy大爷说要,于是我加上就过了
	}
	printf("%lld", answer);
}

以上是关于jzoj3505NOIP2013模拟11.4A组组合逆元积木的主要内容,如果未能解决你的问题,请参考以下文章

jzoj3523NOIP2013模拟11.7A组树上倍增JIH的玩偶(tree)

jzoj3528NOIP2013模拟11.7A组数学拓扑DP图书馆(library)

jzoj3510NOIP2013模拟11.5B组DAY 1 (7.12)DP最短路径(path)

jzoj3508NOIP2013模拟11.5B组DAY 1 (7.12)HASH好元素(good)

[jzoj]3456.NOIP2013模拟联考3恭介的法则(rule)

jzoj3515NOIP2013模拟11.6B组二分DP软件公司