洛谷 CF466D Increase Sequence 蓝 题解
前言
看了题解才会的……决定自己详细写一篇保证自己学明白了!
思路
题目给我印象最深的一句话是:任意两个区间的左右端点不重合。感觉这个要求挺苛刻的,你会发现 \(a\) 数组的第 \(1\) 项和最后一项最多和 \(h\) 相差 \(1\)
这个不难理解,因为想要让第一项 \(+1\),你的左端点必须是 \(1\),那么以后的所有操作的左端点都不能是 \(1\) 了,自然只能操作一次,最后一项同理。
所以决定因素在于 \(a_i\) 和 \(h\) 相差多少,那么我们把 \(a_i\) 的意义变一下,如果原序列为 \(a_i\),那么新的 \(a_i\) 表示 \(h - a_i\)(接下来的所有 \(a_i\) 表示的均为最新意义)
分析每次操作,改变的是一个区间且加的值是固定的,如果暴力修改复杂度不可承受,考虑有没有一种 \(O(1)\) 的修改方式?差分!
令 \(d_i=a_i-a_{i-1}\),那么每次区间为 \([l,r]\) 的操作,等价于 \(d_l-1,d_{r+1}+1\),由于端点不能重复,所以每个 \(d_i\) 至多会被减一次,那么无解情况就出来了:若存在 \(|d_i|>1\),必然无解!(请注意,现在的操作为区间减,因为 \(a\) 数组的意义已经变了)
最终合法的状态为:\(d\) 数组全为 \(0\),而可能的 \(d_i\) 的值也就 \(3\) 种:\(1,-1,0\),逐个分析即可。
- 若 \(d_i\) 为 \(1\),说明必须要有一个操作的左端点为 \(i\),也就是说左端点数量要加一
- 若 \(d_i\) 为 \(-1\),说明必须要有一个操作的右端点为 \(i\),那么这个右端点可以和之前的任何一个左端点匹配,完毕后左端点数量要减一,因为一个端点不能同时做两次左端点。
- 若 \(d_i\) 为 \(0\),该怎么办?在此之前,我想先明确一下题意,题意指:一个端点不能作多次左端点,也不能作多次右端点,但是可以作一次左端点再作一次右端点。所以对于 \(0\) 的情况,要么先让它作右端点,和之前的任意一个左端点匹配,然后再作左端点,来保证 \(0-1+1=0\);要么干脆就不动它,即不对其进行操作。请注意:这里不需要改变左端点数量,因为在第一种情况中既作右端点又作左端点就抵消了,第二种情况下无事发生……
Code
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 2010;
const LL mod = 1e9 + 7;
int n, h, num;
int a[N], diff[N];
LL ans = 1;
int main()
{
cin >> n >> h;
for (int i = 1; i <= n; i ++ )
{
cin >> a[i];
a[i] = h - a[i];
}
for (int i = 1; i <= n + 1; i ++ )
{
diff[i] = a[i] - a[i - 1];
if (abs(diff[i]) > 1) return cout << 0 << endl, 0;
}
for (int i = 1; i <= n; i ++ )
{
if (diff[i] == 1) num ++ ;
if (diff[i] == 0) ans = ans * (num + 1) % mod;
if (diff[i] == -1) ans = ans * num % mod, num -- ;
}
cout << ans << endl;
return 0;
}
后记
花了好长时间才理解,完结撒花!