P8497 [NOI2022] 移除石子
D1T2. P8497 [NOI2022] 移除石子
这道题目比较抽象,比较人类智慧。
对于合法序列计数题,首先必须会判定合法序列,即考虑
从最基本的
-
区间长度要求
\geq 3 等价于区间长度为3, 4, 5 ,经典套路。 -
不会进行相同的操作二,可用若干操作一代替。
设计类插头 DP
根据分析,
- 到
i - 1 长度已经等于3 且还要继续延伸到\geq i 的位置的区间只可能是[i - 3, i] ,[i - 3, i + 1] 和[i - 4, i] ,因此j\leq 3 。 - 从每个位置开始的操作二数量
\leq 3 ,因此k, l\leq 3 。
上述做法已经可以通过该档部分分。
进一步地,我们发现
因此,设
类似分析,可知
进一步挖掘性质,我们发现
- 若从一个位置开始同时有长度为
4, 5 的操作二,可以简化成一次操作一和从下一个位置开始长度为3, 4 的操作二。
因此
进一步地,我们枚举跨过
时间复杂度
考虑
-
若
k = 0 显然不影响。 -
若
k = 1 ,考虑不添加任何石子的有解局面最终方案:- 若存在至少一次操作一则必然有解,直接将石子放在操作一位置上即可。
- 若存在长度
> 3 的操作二则必然有解,将石子放在操作二左端变成一次操作一和一次操作二即可。 - 若存在长度
= 3 的操作二且n > 3 则必然有解,将石子放在操作二两侧某空位上(n > 3 故存在),变成一次长度= 4 的操作二即可。
综合上述分析,可知非法当且仅当
n = 3 且a_1 = a_2 = a_3 = 1 ,或a_i = 0 。 -
若
k = 2 :- 若
k = 0 有解,将两颗石子放在任意位置用一次操作一处理,得出k = 2 有解。 - 若
k = 1 有解,考察所有 添加一颗石子,有解变无解 的局面,即n = 3 且a_1 = a_2 = a_3 = 1 ,或a_i = 0 。去掉一颗石子,要么没有石子可以去掉,即这种情况不可能发生,要么总可以重新添加两颗石子使得有解。
综合上述分析,若局面添加小于两颗石子有解,则添加两颗石子有解。
- 若
-
同理可证对于任意
k > 2 ,若局面添加小于k 颗石子有解,则添加k 颗石子有解。
特判掉
综上,设
时间复杂度
从
由于之前我们已经对状态进行了充足的简化,
通过对拍我们发现
对于最终态
将对
时间复杂度
总结:D1T2 和 D2T2 都是从简化问题入手,通过结合两个问题维度上的扩展得到正解,做这两道题让我受益匪浅。这让我想到了平行四边形,给它加上对角线相等的条件就成了矩形,给它加上邻边相等的条件就成了菱形,将两者结合在一起就成了正方形。有的时候从平行四边形到正方形是困难的,但从平行四边形到矩形和菱形是自然的,而它们的结合也是自然的。这给出了我们思考问题的一条有效的道路,同时也说明了为什么从部分分开始思考,一步一步打暴力是优秀的。我希望大家都能体会到这一点。
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
using ll = long long;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
inline int read() {
int x = 0, sgn = 0;
char s = getchar();
while(!isdigit(s)) sgn |= s == '-', s = getchar();
while(isdigit(s)) x = x * 10 + s - '0', s = getchar();
return sgn ? -x : x;
}
inline void print(int x) {
if(x < 0) return putchar('-'), print(-x);
if(x >= 10) print(x / 10);
putchar(x % 10 + '0');
}
bool Mbe;
constexpr int N = 1e3 + 5;
constexpr int S = 8765;
constexpr int mod = 1e9 + 7;
void cmin(int &x, int y) {x = x < y ? x : y;}
void add(int &x, int y) {x += y, x >= mod && (x -= mod);}
int n, k, l[N], r[N], node, f[S], g[S], tr[S][7];
map<vector<int>, int> mp;
int dfs(vector<int> cur) {
if(mp.find(cur) != mp.end()) return mp[cur];
int ind = mp[cur] = node++;
for(int a = 0; a < 7; a++) {
vector<int> suc(9, 101);
for(int j = 0; j < 3; j++)
for(int k = 0; k < 3; k++) {
int v = cur[j * 3 + k];
if(v == 101) continue;
for(int st = 0; st < 3; st++) {
int val = a - j - k - st;
int need = val < 0 ? -val : val == 1 ? 1 : 0;
for(int nj = k; nj <= min(2, j + k); nj++) cmin(suc[nj * 3 + st], v + need);
}
}
tr[ind][a] = dfs(suc);
}
return ind;
}
void solve() {
cin >> n >> k;
for(int i = 1; i <= n; i++) cin >> l[i] >> r[i];
memset(f, 0, sizeof(f)), f[0] = 1;
for(int i = 1; i <= n; i++) {
memset(g, 0, sizeof(g));
for(int j = 0; j < S; j++) {
if(!f[j]) continue;
for(int a = 0; a < 7; a++) {
int coef = 0;
if(a < 6) coef = l[i] <= a && a <= r[i];
else if(r[i] >= 6) coef = r[i] - max(6, l[i]) + 1;
if(coef == 1) add(g[tr[j][a]], f[j]);
else if(coef > 0) add(g[tr[j][a]], 1ll * f[j] * coef % mod);
}
}
swap(f, g);
}
int ans = 0;
for(auto it : mp) if(it.first[0] <= k) add(ans, f[it.second]);
if(k == 1) {
bool all0 = 1;
for(int i = 1; i <= n; i++) all0 &= !l[i];
if(all0) ans--;
if(n == 3 && l[1] <= 1 && r[1] >= 1 && l[2] <= 1 && r[2] >= 1 && l[3] <= 1 && r[3] >= 1) ans--;
}
cout << (ans % mod + mod) % mod << "\n";
}
bool Med;
int main() {
fprintf(stderr, "%.3lf MB\n", (&Mbe - &Med) / 1048576.0);
#ifdef ALEX_WEI
FILE* IN = freopen("b.in", "r", stdin);
FILE* OUT = freopen("b.out", "w", stdout);
#endif
vector<int> I(9, 101);
I[0] = 0, dfs(I);
cerr << node << endl;
int T;
cin >> T;
while(T--) solve();
cerr << TIME << " ms\n";
return 0;
}
/*
2022/8/31
author: Alex_Wei
start coding at 17:50
finish debugging at 21:17
*/