题目
对于一个长度为 \(n\) 的正整数序列 \(\{a\}\) 和参数 \(K\),你可以进行如下三种操作:
-
选择一个 \(1\le i\le n\),满足 \(a_i\ge 2\),并选取一个 \(2\le k\le a_i\),令 \(a_i\gets a_i-k\)。
-
选择一个连续的编号区间 \([l,r]\) 满足 \(1\le l,l+2\le r\le n,\min_{l\le i\le r}a_i>0\),令 \(l\le i\le r\) 的每一个 \(a_i\gets a_i-1\)。
-
选择一个 \(1\le i\le n\),令 \(a_i\gets a_i+1\)。
第三种操作必须在前两种操作之前进行,且必须恰好进行 \(K\) 次。而进行完第三种操作之后,你可以按任意顺序执行任意多次前两种操作。如果存在一种操作的方式,使得 \(\{a\}\) 可以变成一个全 \(0\) 的序列的话,我们就说 \(\{a\}\) 是“好”的序列。
现在,给定 \(n\) 和 \(K\),以及 \(n\) 对参数 \(\{(L_i,R_i)\}\)。如果我们允许 \(a_i\) 取 \([L_i,R_i]\) 中任意整数,则所有的可能的 \(\{a\}\) 中,有多少个是“好”的?答案对 \(10^9+7\) 取模。
对于 \(100\%\) 的数据,满足 \(3\le n\le 1000,0\le K\le 100, 0\le L_i\le R_i\le 10^9\)。
多组数据,单个测试点数据组数不超过 \(10\) 组。
分析
GG 了,一上来连 DP 套 DP 都没想到......
25 pts
你这出题人给分也太吝啬了。
首先考虑,怎么进行 \(L_i=R_i\) 的检查?我最开始的想法是针对序列本身进行讨论,这可以在 \(R_i\le 2\) 的时候生效,但深入下去就很恼火了。
想一想为什么?直接讨论的困难在于,可能的操作方案实在是太多了。凭借直觉构造无异于盲目搜索,更何况我向来不擅长这种东西。因此,我们首先需要做的应当是将操作归约到某些特殊情况上。
从这个角度入手,我们不难想到如下几个事实:
Conclusion.
任意操作二的编号区间长度 \(\le 5\)。
不存在两次操作二操作的编号区间完全一样。
Proof.
\(\ge 6\) 的,对半剖开一定可以变成两个不短于 \(3\) 的。
如果出现了两次以上对于 \([l,r]\) 的操作,我们可以转而对于 \(l\le i\le r\) 的每一个 \(a_i\) 执行相同数量的操作一。
Note.
这种复杂操作的问题,首先研究操作的特性、归约的方法应该是基础的思路。尤其需要关注多种操作互相配合产生的效果。
我们的目标就应该是,尽量把涉及到的操作变短、变少、变简洁。
现在,从每个位置起始的操作二只有至多三次(对应长度为 \(3,4,5\) 的操作区间),而操作一非常灵活。因此,我们可以想到确定操作二的情况进行检查,而实际上从前往后扫描时,我们只需要知道较近的若干个操作二的情况即可。
因此,可以设计 DP:\(f_{i,a,b,c,d}\) 表示到第 \(i\) 个位置时,\(i-4\sim i-1\) 的操作二区间个数分别为 \(a,b,c,d\) 时,前缀 \(i\) 能否被清除完。进一步地,由于转移过程只需要考虑多少个操作二区间被 terminate 了,\(a,b,c\) 实际上是本质相同的,状态可以被改写成 \(g_{i,e,d},e=a+b+c\)。
很明显我们需要关注 \(e,d\) 的范围。现在是 \(0\le e\le 9,0\le d\le 3\)。更深入的分析表明范围可以缩小,但在这个部分里已经够用了。
40 pts
你这出题人给分也太吝啬了。
此时需要考虑 \(K\) 的影响。直觉是,\(K\) 和“好不好”应该存在单调性,但是(原)题面似乎否定了这一点。那么,我们就来看一下单调性的问题。
单调性其实就是看“好的序列再执行一次操作三是否仍然好”,指向的是操作的调整。从这一点入手,我们可以得出:
Conclusion.
当且仅当 \(n\) 和“好”的序列 \(\{a\}\) 满足下列条件之一时,对于 \(\{a\}\) 多进行一次操作三总会导致 \(\{a\}\) 变得不“好”:
\(n=3,\{a\}=\{1,1,1\}\)。
\(\{a\}=\{0\}_{i=1}^n\)。
Proof.
正向的分析。
首先,如果某种操作方案中存在操作一,我们可以直接在这个操作一的位置上进行操作三,然后相应地增加操作一的 \(k\)。
其次,如果某种操作方案中存在操作二,且对应的 \([l,r]\neq [1,n]\),我们可以在 \(l-1\) 或者 \(r+1\) 处进行操作三,然后扩展操作二的区间。
现在,可能变不“好”的序列只有全 \(1\) 的序列和全 \(0\) 的序列。在 \(n>3\) 时,对于全 \(1\) 的序列而言,我们可以对于 \(a_1\) 进行操作三,然后添加一次针对 \(a_1\) 的操作一,最后进行一次 \([2,n]\) 的操作二。
最后就可以得到上面的结论。
Note.
具体操作的核心思路是:对于已有的方案执行微调。在已有的方案上面施工是便利的,轻易抛弃它常常是愚蠢的。
也就是说,我们可以毫不费力地将 \(g\) 改成对于“最小进行操作三次数”的 DP。最后只需要额外判断一下两种不合单调性的情况即可。
55 pts
现在进入了计数问题,我们也自然地想到了 DP 套 DP 的思路。
问题是,即便是布尔 DP,\(g_i\) 也有足足 \(40\) 个值,我们需要压缩。
第一步压缩比较简单。如果以某个位置出发的操作二区间真的有 \(3\) 个,我们其实可以通过操作二,将长度为 \(4,5\) 的切掉一部分:
1 --- 1 ---
2 ---- => 2 |---
3 ----- 3 |----
横线表示操作二区间,竖线表示操作一区间。
那么,\(g_{i,e,d}\) 的 \(e,d\) 可以被压缩到 \(0\le e\le 6,0\le d\le 2\),总共 \(18\) 个值。如果常数充分小,跑一跑 \(2^18\) 的 DP 说不定可以过(。
再压缩就很困难了。\(d\) 似乎已经走到了尽头,但是 \(e\) 真的需要这么大吗?实际上,
Note.
个人认为,这里的难点在于“想到并相信 \(e\) 的上界可以继续收缩”。这有点反直觉,但是我们必须看明白这样一个事实,就是 \(e\) 是一个合并过的变量,\(d\) 是单个的变量。\(d\le 2\) 可能是紧的,但是 \(e\le 6\) 可能还是松的,也必须意识到多个操作组合起来威力巨大。
想到这一点之后,后续的检查其实相对简单。为什么呢?因为有了参考答案,可以对拍验证。
最终,我们可以压缩到 \(0\le e\le 2,0\le d\le 2\)。此时跑 DP 套 DP 就不难了。
注意在这个范围下,\(a\) 实际上只需要枚举到 \(6\),\(\ge 6\) 的 \(a\) 没有本质区别。
100 pts
是的,这个部分分分布实在是太糟糕了。
此时内层的 \(g\) 是对于“最小值”的 DP。从暴力的角度来考虑,我们仍然可以执行先前的 DP 套 DP,只不过现在的内层状态达到了 \((K+2)^9\) 之巨。
现在你会想什么?放弃这个做法,还是 tricky 地使用 std :: map
来存放状态?
肯定选择后者啊!明显可以和之前的东西兼容嘛,而且容易从 55pts 的代码改过来,写起来肯定容易。写对了就好了,过不过得了根本不归我管。
相当有效。实际上,可能遇到的状态只有 \(8000\sim 9000\) 个。这个做法可以进一步优化——我们提前将所有状态搜出来,顺便把转移表也打出来,这就相当于建立了一个 DFA,之后对着转移即可。
Note.
DP 内层实际上就是装了一个 DFA。而这告诉我们,看起来状态数很多的 DFA,实际上能用的状态可能根本没多少。
因此,如果再遇到一个很大的 DFA,我们应当尝试搜索一下它的有效状态再评估思路的可行性。
代码
#include <map>
#include <cstdio>
#include <vector>
#include <cstring>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
typedef long long LL;
const int mod = 1e9 + 7;
const int MAXN = 1e3 + 5, MAXS = 9e3 + 5;
template<typename _T>
inline void Read( _T &x ) {
x = 0; char s = getchar(); bool f = false;
while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}
template<typename _T>
inline void Write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) Write( x / 10 );
putchar( x % 10 + '0' );
}
template<typename _T>
inline _T Min( const _T &a, const _T &b ) {
return a < b ? a : b;
}
template<typename _T>
inline _T Max( const _T &a, const _T &b ) {
return a > b ? a : b;
}
std :: map<LL, int> mp;
int tsf[MAXS][9], ntot = 0;
bool imp[MAXS];
int f[2][MAXS];
int dp[2][3][3];
int L[MAXN], R[MAXN];
int N, K;
inline int Mul( int x, const int &v ) { return 1ll * x * v % mod; }
inline int Sub( int x, const int &v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, const int &v ) { return ( x += v ) >= mod ? x - mod : x; }
inline int& MulEq( int &x, const int &v ) { return x = 1ll * x * v % mod; }
inline int& SubEq( int &x, const int &v ) { return ( x -= v ) < 0 ? ( x += mod ) : x; }
inline int& AddEq( int &x, const int &v ) { return ( x += v ) >= mod ? ( x -= mod ) : x; }
inline bool TestAllZero() {
rep( i, 1, N )
if( L[i] > 0 )
return false;
return K == 1;
}
inline bool TestAllOne() {
rep( i, 1, N )
if( R[i] < 1 || 1 < L[i] )
return false;
return N == 3 && K == 1;
}
int DFS( const LL hsh ) {
if( mp.find( hsh ) != mp.end() ) return mp[hsh];
int cur = ++ ntot;
imp[mp[hsh] = cur] = ( hsh % ( K + 2 ) ) <= K;
rep( v, 0, 8 ) {
LL tmp = hsh;
rep( i, 0, 2 ) rep( j, 0, 2 ) {
dp[0][i][j] = tmp % ( K + 2 ), tmp /= K + 2;
dp[1][i][j] = K + 1;
}
rep( a, 0, 2 ) rep( b, 0, 2 ) {
if( dp[0][a][b] > K ) continue;
rep( p, 0, a ) rep( r, 0, 2 ) if( a - p + b <= 2 ) {
int t = v - a - b - r;
dp[1][a - p + b][r] = Min( dp[1][a - p + b][r], dp[0][a][b] + Max( - t, 0 ) + ( t == 1 ) );
}
}
tmp = 0;
per( a, 2, 0 ) per( b, 2, 0 )
tmp = tmp * ( K + 2 ) + dp[1][a][b];
tsf[cur][v] = DFS( tmp );
}
return cur;
}
int main() {
int T; Read( T );
while( T -- ) {
Read( N ), Read( K );
rep( i, 1, N ) Read( L[i] ), Read( R[i] );
ntot = 0, mp.clear(); LL beg = 0;
per( i, 8, 1 ) beg = beg * ( K + 2 ) + K + 1;
DFS( beg * ( K + 2 ) );
rep( i, 1, ntot ) f[0][i] = f[1][i] = 0;
int pre = 1, nxt = 0; AddEq( f[nxt][1], 1 );
rep( i, 1, N ) {
pre ^= 1, nxt ^= 1;
rep( j, 1, ntot ) if( f[pre][j] ) {
for( int k = L[i] ; k <= R[i] && k < 8 ; k ++ )
AddEq( f[nxt][tsf[j][k]], f[pre][j] );
if( R[i] >= 8 ) AddEq( f[nxt][tsf[j][8]], Mul( f[pre][j], ( R[i] - Max( L[i], 8 ) + 1 ) % mod ) );
f[pre][j] = 0;
}
}
int ans = 0;
rep( j, 1, ntot ) if( imp[j] && f[nxt][j] )
AddEq( ans, f[nxt][j] );
if( TestAllZero() ) SubEq( ans, 1 );
if( TestAllOne() ) SubEq( ans, 1 );
Write( ans ), putchar( '\n' );
}
return 0;
}