长春集训 Day 8 - 模拟赛、DP 专题
长春集训 Day 8 - 模拟赛、DP 专题
模拟赛 100+0+0+0,打暴力的能力还得增强
T1 - 画圈
Description
给定 \(n\) 个点,\(m\) 条边的连通简单无向图,每条边初始为黑色或白色。可以进行若干次如下操作:
- 选择图中至少包含一条白色边的简单环,并把这个环上的所有边都染成黑色。
可以在任意时刻停止操作,不要求图中所有边最终都被染成黑色。求最多可以执行多少次操作。
多测,\(1 \le T \le 10^4,\ 1 \le n,m,\sum n,\sum m \le 3 \times 10^5\)。
Solution
注意到每次只染黑一个白边一定最优。可以构造出一些数据,使得其中有“不得不染黑多条白边”的情况,这种情况下,每多染黑一条白边,一定会把两个黑边构成的连通块合并。所以记黑边构成的连通块有 \(b\) 个,那么最多合并 \(b-1\) 次。记白边数量为 \(a\),则答案为 \(a - (b-1)\)。
另解 对图中所有黑边跑出生成树森林,然后添加若干白边使它们连通,构建出原图白边数最少的最小生成树,与上式等价。
Code
#include <bits/stdc++.h>
// #pragma GCC optimize(2)
#define int long long
#define inf 1e18
#define debug cout << '!';
#define filein(x) freopen(#x".in", "r", stdin);
#define fileout(x) freopen(#x".out", "w", stdout);
#define file(x) filein(x) fileout(x)
using namespace std;
const int N = 2e5+5;
int T, n, m;
bool flg[N];
struct DSU {
int fa[N];
void init() {
for (int i = 1; i <= n; i++) {
flg[i] = 0;
fa[i] = i;
}
}
int find(int x) {
if (x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
void unite(int x, int y) {
fa[find(x)] = find(y);
}
} dsu;
void solve() {
cin >> n >> m;
dsu.init();
int cnt = 0, tot = 0;
for (int i = 1; i <= m; i++) {
int u, v, w; cin >> u >> v >> w;
if (w == 0) {
cnt++;
} else {
dsu.unite(u, v);
}
}
for (int i = 1; i <= n; i++) {
flg[dsu.find(i)] = 1;
}
for (int i = 1; i <= n; i++) {
if (flg[i] == 1) tot++;
}
cout << cnt - tot + 1 << '\n';
}
signed main() {
cin.tie(0) -> sync_with_stdio(0);
cin >> T;
while (T--) {
solve();
}
return 0;
}
T3 - No explanation
Description
给定 \(n\) 个只包含小写字母的字符串数组 \(s\),要求从中选出一些字符串,并选择一个合适的顺序将它们首位拼接成一个新字符串 \(S\),且保证 \(S\) 中不存在子序列 \(\texttt{luolikong}\)(无需连续)。请计算 \(S\) 的最大长度。
\(1 \le n \le 1000, 1 \le |s_i|, \sum |s_i| \le 10^5\)。
Solution
记 \(t = \texttt{luolikong}\)。由于我们只关心是否会出现子序列 \(t\),因此可以建立有限状态自动机,探明状态之中的转移关系。
定义状态 \(j(0 \le j \le 9)\) 表示目前已经匹配了 \(t\) 的前 \(j\) 个字符,其中 \(j=9\) 表示全部匹配,为非法状态。
每添加一个字符串都会产生一个转移关系,因此定义 \(f_{i,j}\) 表示当前匹配状态为 \(j\) 时,从头到尾读入字符串 \(s_i\) 后,匹配状态会变为什么。可以用双指针计算 \(f\) 数组的值。
另外,每拼接一个字符串都会产生大于或等于 \(0\) 的贡献,因此状态转移是单调不降的。
当把若干个字符串按某种顺序拼接时,等价于在 \(0 \to 8\) 上走一条路径。
对于能使状态前进一步的字符串,我们称之为“前向字符串“;对于使得状态在某点不动的字符串,我们称之为”自环字符串“。可能的状态只有 \(2^8\) 种(第一个字符串一定是前向字符串)。
假设固定状态集 \(X = \{x_0, x_1, \cdots, x_k\}\),现有 \(k\) 条连边 \(x_{j-1} \to x_j\) 需要满足。考虑压位。定义 \(st\) 表示一个二进制状态集,其中第 \(j\) 位表示边 \(x_{j} \to x_{j+1}\) 是否被字符串覆盖。\(dp_{st}\) 表示覆盖 \(st\) 中的连接边的前提下,能获得的最大拼接总长度。
对于每个字符串,只能使用一次,因此类似于一个 01 背包状物,我们可以定义两个 \(dp\) 数组来滚动更新,具体地,我们有如下转移方式:
-
不选该字符串
\[dp'_{st} = \max(dp'_{st}, dp_{st}) \] -
作为自环字符串而加入
如果 \(\exist j \in [0,k]\),使得 \(f_{i,x_j} = x_j\),那么该字符串即为自环。自环不改变状态值,因此有
\[dp'_{st} = \max(dp'_{st}, dp_{st} + len) \] -
作为前向字符串而加入
如果 \(\exist j\in[0,k]\),使得 \(st_j = 0 \land f_{j,x_j} = x_{j+1}\),那么可以更新状态并转移
\[dp'_{st \cup \{j\}} = \max(dp'_{st \cup \{j\}}, dp_{st} + len) \]
最终,如果当前固定的状态集能够覆盖 \(k\) 条连边,即 \(dp_{2^k-1} > -\infin\),则方案合法,更新全局答案。
Code
#include <bits/stdc++.h>
// #pragma GCC optimize(2)
#define int long long
#define inf 1e18
#define debug cout << '!';
#define filein(x) freopen(#x".in", "r", stdin);
#define fileout(x) freopen(#x".out", "w", stdout);
#define file(x) filein(x) fileout(x)
using namespace std;
const int N = 1005, M = 2005, L = 9;
const string t = "luolikong";
int n;
string s[N];
int nxt[N][L], ans;
int dp[M][2];
signed main() {
cin.tie(0) -> sync_with_stdio(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> s[i];
}
for (int i = 1; i <= n; i++) {
for (int st = 0; st < 9; st++) {
int cur = st;
for (auto c : s[i]) {
if (c == t[cur] and cur < 9) {
cur++;
if (cur == 9) break;
}
}
nxt[i-1][st] = cur;
}
}
for (int st0 = 1; st0 < (1<<L); st0 += 2) {
vector<int> dgt;
for (int i = 0; i < 9; i++) {
if ((st0 >> i) & 1) {
dgt.push_back(i);
}
}
int k = dgt.size() - 1;
for (int st1 = 0; st1 < (1<<k); st1++) {
dp[st1][1] = dp[st1][0] = -inf;
}
dp[0][0] = 0;
for (int i = 1; i <= n; i++) {
if (nxt[i-1][0] == 9) continue;
int len = s[i].size();
for (int st1 = 0; st1 < (1<<k); st1++) {
dp[st1][1] = dp[st1][0];
}
for (int st1 = 0; st1 < (1<<k); st1++) {
if (dp[st1][0] <= -inf/2) continue;
for (int j = 0; j <= k; j++) {
if (dgt[j] == nxt[i-1][dgt[j]]) {
dp[st1][1] = max(dp[st1][1], dp[st1][0] + len);
}
}
for (int j = 0; j < k; j++) {
if (st1 & (1<<j)) continue;
if (dgt[j+1] == nxt[i-1][dgt[j]]) {
dp[st1|(1<<j)][1] = max(dp[st1|(1<<j)][1], dp[st1][0] + len);
}
}
}
for (int st1 = 0; st1 < (1<<k); st1++) {
dp[st1][0] = dp[st1][1];
}
}
if (dp[(1<<k)-1][0] > -inf/2) {
ans = max(ans, dp[(1<<k)-1][0]);
}
}
cout << ans;
return 0;
}

浙公网安备 33010602011771号