D1. Inversion Graph Coloring (Easy Version)
题意:
给定一个序列 \(a_1, a_2, \ldots, a_n\),我们需要计算其“好”子序列的数量。一个子序列是“好”的,如果存在一种将它的索引染成红色或蓝色的方式,使得对于任何一对索引 \(i < j\) 且子序列中 \(b_i > b_j\),索引 \(i\) 和 \(j\) 的颜色不同。换句话说,子序列的逆序图是二分图。
思路:
经分析,一个子序列是“好”的当且仅当它不包含长度为 3 的递减子序列(即没有三个元素 \(b_i > b_j > b_k\) 且 \(i < j < k\))。因此,问题转化为计算序列中所有不包含长度为 3 的递减子序列的子序列的数量
由于 $n \leq 300 $ 且所有测试用例的 $ n $ 总和不超过 300,我们可以使用动态规划(DP)来高效计数。
DP 状态设计
定义 DP 状态 ( dp[j][k] ) 表示当前子序列的最大值为 ( j )、次大值为 ( k ) 的子序列数量。其中 ( j ) 和 ( k ) 是序列中的值(范围从 0 到 ( n ),0 用于表示空状态)。初始状态 ( dp[0][0] = 1 ) 代表空子序列。
状态转移
遍历序列中的每个元素$ ( a_i )$ 时,对于每个状态 \((j, k)\) ,我们考虑两种选择:
- 不选当前元素:状态不变,即 $ dp[j][k] $ 贡献到新状态 $ cp[j][k]$。
- 选当前元素:根据 $ a_i $ 与当前最大值 $ j $ 和次大值 $ k $ 的关系更新状态:
- 如果 \(( a_i \geq j )\),添加后新最大值为 ( a_i ),次大值为 ( j ),状态转移到 ( cp[a_i][j] )。
- 如果 \(( a_i < j )\) 但 \(( a_i \geq k )\),添加后最大值不变,次大值更新为 \(( a_i )\),状态转移到 \(( cp[j][a_i] )\)。
- 如果 \(( a_i < k )\),添加后会形成三个递减元素 \(( j > k > a_i )\),因此不能选,跳过。
这样确保所有计数的子序列都不包含长度为 3 的递减子序列。
代码
#include<bits/stdc++.h>
#define ll long long
#define ce cerr
#define ull unsigned long long
#define lll __int128
#define PII pair<int, int>
#define PLL pair<long ,long>
#define int long long
using namespace std;const int inf = 0x3f3f3f3f;
const ll iinf = 1e18;
const int N = 310;
const int mod = 1e9 + 7;int t;void solve() {int n;cin >> n;vector<int> a(n + 1);for (int i = 0; i < n; ++i) {cin >> a[i];}// dp[j][k] 表示当前子序列的最大值为 j,次大值为 k 的子序列数量vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));dp[0][0] = 1; // 初始状态:空子序列for (int i = 0; i < n; ++i) {vector<vector<int>> cp(n + 1, vector<int>(n + 1, 0)); // 临时状态矩阵for (int j = 0; j <= n; ++j) {for (int k = 0; k <= n; ++k) {// 不选当前元素 a[i]cp[j][k] = (cp[j][k] + dp[j][k]) % mod;// 选当前元素 a[i]if (a[i] >= j) {// 新最大值是 a[i],次大值是 jcp[a[i]][j] = (cp[a[i]][j] + dp[j][k]) % mod;} else if (a[i] >= k) {// 最大值不变,次大值更新为 a[i]cp[j][a[i]] = (cp[j][a[i]] + dp[j][k]) % mod;} else {// 如果 a[i] < k,添加会形成三个递减,跳过continue;}}}dp = cp; // 更新状态}ll res = 0;// 求和所有状态的值for (int i = 0; i <= n; ++i) {for (int j = 0; j <= n; ++j) {res = (res + dp[i][j]) % mod;}}cout << res << "\n";
}signed main() {ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);t = 1;cin >> t;while (t--) {solve();}return 0;
}
D2. Inversion Graph Coloring (Hard Version)
题意:
同前,でも\(n = 2000\)
思路:
原 \(dp[j][k]\) 表示:已经选的子序列中两种颜色各自最后一个元素的值(分别记为 \(j\)、\(k\))。
遍历新元素 \(v = a[i]\) 时,只会修改两类位置:行 \(\boxed{j == v}\) (把元素放到颜色1)和列 \(\boxed{k == v}\) (把元素放到颜色2,且 \(j > v\))。因此不必在每一步重建整个 \(n \times n\) 表格,只需要对这两类位置做增量更新。
对于行更新需要按列查询列前缀和:\(\sum_{j=0}^{v} dp[j][k]\),为此给每一列维护一个 Fenwick(按 \(j\) 的维度做前缀和)。
对于列更新需要按行查询行前缀和:\(\sum_{k=0}^{v} dp[j][k]\),为此给每一行维护一个 Fenwick(按 \(k\) 的维度做前缀和)。
每次把增量写回 \(dp\) 的同时,立刻更新对应的行/列 Fenwick(因为这些增量只会影响未来元素的查询,但对本次同一列/同行的查询顺序不会产生交叉影响,按上面顺序逐项处理即可安全使用)。
代码
#include<bits/stdc++.h>
#define ll long long
#define ce cerr
#define ull unsigned long long
#define lll __int128
#define PII pair<int, int>
#define PLL pair<long ,long>
#define int long long
using namespace std;const int inf = 0x3f3f3f3f;
const ll iinf = 1e18;
const int N = 310;
const int MOD = 1e9 + 7;
//cin.ignore(std::numeric_limits< streamsize >::max(), '\n');
int t;
//dp[i][j]
struct Fenwick {int n; vector<int> bit;Fenwick() : n(0) {}Fenwick(int _n) { init(_n); }void init(int _n) {n = _n; // we will use positions 0..(n-1) if _n is countbit.assign(n + 5, 0);}// add val to position pos (pos in [0..n-1])void add(int pos, int val) {if (val == 0) return;int i = pos + 1;while (i <= n) {bit[i] += val;if (bit[i] >= MOD) bit[i] -= MOD;i += i & -i;}}// sum of [0..pos], pos in [-1..n-1]int sumPrefix(int pos) {if (pos < 0) return 0;int i = pos + 1;int res = 0;while (i > 0) {res += bit[i];if (res >= MOD) res -= MOD;i -= i & -i;}return res;}
};
void solve() {int n;cin >> n;vector<int> a (n + 1);for (int i = 0; i < n; ++i) {cin >> a[i];}// dp[j][k], j,k in [0..n]vector<vector<int> > dp (n + 1, vector<int> (n + 1, 0)); dp[0][0] = 1;// vec1 = rowFen[j] (for fixed j, prefix over k)// vec2 = colFen[k] (for fixed k, prefix over j)vector<Fenwick> vec1 (n + 1), vec2 (n + 1);for (int i = 0; i <= n; ++i) {vec1[i].init(n + 1);vec2[i].init(n + 1);}// initial statevec1[0].add(0, 1);vec2[0].add(0, 1);for (int idx = 0; idx < n; ++idx) {int v = a[idx]; // 1..n// 1) 把当前元素放到“颜色1”的末尾 -> 更新行 j == v:// 对每个 k in [0..n]: dp[v][k] += sum_{j=0..v} dp[j][k] (colFen[k].sumPrefix(v))for (int k = 0; k <= n; ++k) {int addVal = vec2[k].sumPrefix(v);if (addVal == 0) continue;dp[v][k] += addVal;if (dp[v][k] >= MOD) dp[v][k] -= MOD;vec1[v].add(k, addVal); // 更新 rowFen[v] 的 k 位置vec2[k].add(v, addVal); // 更新 colFen[k] 的 j=v 位置}// 2) 把当前元素放到“颜色2”的末尾 -> 更新列 k == v,但只有 j > v 才走这条// 对每个 j in (v..n]: dp[j][v] += sum_{k=0..v} dp[j][k] (rowFen[j].sumPrefix(v))for (int j = v + 1; j <= n; ++j) {int addVal = vec1[j].sumPrefix(v);if (addVal == 0) continue;dp[j][v] += addVal;if (dp[j][v] >= MOD) dp[j][v] -= MOD;vec1[j].add(v, addVal);vec2[v].add(j, addVal);}}ll res = 0;for (int i = 0; i <= n; ++i) {for (int j = 0; j <= n; ++j) {res = (res + dp[i][j]) % MOD;}}cout << res << "\n";
}
signed main() {ios::sync_with_stdio (false);cin.tie(NULL);cout.tie(NULL);t = 1;cin >> t;while (t --) {solve();}return 0;
}