贪心 + 模拟,计数,AC 自动机 + 状压 dp,神秘题
# A. 魔法(magic)
考场上理解错题意了,几乎爆零。不过似乎大家也都因为被输出卡没了(雾
那么实际上就是一道拿栈模拟的题。
令 ,,依次考虑每一个球。
如果当前栈顶的 个元素的和符合条件,那么把栈顶 个元素直接删掉。
把当前球入栈。
感性理解一下发现这东西非常正确。
#include <bits/stdc++.h> | |
#define pb push_back | |
using namespace std; | |
const int N = 2e5 + 10; | |
int n, R, B; | |
char s[N]; | |
int stk[N], sum[N], top, cnt; | |
vector <int> ans[N]; | |
signed main(){ | |
ios :: sync_with_stdio(false); | |
cin >> n >> R >> B >> (s + 1); | |
if((!B && !R) || n % (R + B)) return cout << "NO\n", 0; | |
int s0 = 0, s1 = 0; | |
for(int i = 1; i <= n; ++i) | |
s0 += (s[i] == 'R'), s1 += (s[i] == 'B'); | |
if(!B || !R){ | |
if((!B && s1) || (!R && s0)) return cout << "NO\n", 0; | |
if(!B){ | |
cout << "YES\n" << n / R << '\n'; | |
for(int i = 1; i <= n / R; ++i){ | |
for(int j = (i - 1) * R + 1; j <= i * R; ++j) cout << j << ' '; | |
cout << '\n'; | |
} | |
}else{ | |
cout << "YES\n" << n / B << '\n'; | |
for(int i = 1; i <= n / B; ++i){ | |
for(int j = (i - 1) * B + 1; j <= i * B; ++j) cout << j << ' '; | |
cout << '\n'; | |
} | |
} | |
return 0; | |
} | |
if(n % (R + B) || s0 % R || s1 % B || s0 / R != s1 / B) return cout << "NO\n", 0; | |
for(int i = 1; i <= n; ++i){ | |
stk[++top] = i, sum[top] = sum[top - 1] + (s[i] == 'R' ? 1 : -1); | |
if(top >= R + B && (sum[top] - sum[top - R - B]) == R - B){ | |
cnt++; | |
for(int j = top - R - B + 1; j <= top; ++j) ans[cnt].pb(stk[j]), sum[j] = 0; | |
top -= (R + B); | |
} | |
} | |
if(top) return puts("NO"), 0; | |
reverse(ans + 1, ans + 1 + cnt); | |
cout << "YES\n" << cnt << '\n'; | |
for(int i = 1; i <= cnt; ++i){ | |
for(auto x : ans[i]) cout << x << " "; | |
cout << '\n'; | |
} | |
return 0; | |
} |
# B. 连通性(floyd)
挺复杂的计数题。
50 分应该还是比较好拿的, 的情况就是贝尔数第 项,也即第二类斯特林数前缀和。
下面说正解。
观察 运行机制,我们设前 个点为黑点,后 个点为白点。
那么合法的图一定是下面两种情况之一:
黑白点都有的连通块,对于任意点对 都能找到一条路径满足除端点以外都是黑点的路径。
都是白点的连通块,一定是完全图。
也就是说如果一个连通块里有黑点,那么黑点的导出子图必须连通,且每个白点至少与一个黑点相连。
然后就可以 了。
先设 表示 个点的简单无向连通图个数。
转移是经典容斥:总的 - 不连通的个数。枚举 1 所在连通块大小:
不解释了。
然后设 表示黑点有 个,白点 个时的答案,那么有转移:
- 全是白点,枚举最后一个连通块内有几个点:
- 有黑点,枚举 1 的连通块内几黑几白:
这个 表示 个黑点和 个白点连通的方案数,有:
就是 个黑点组成连通图的方案数 ,每个白点至少和一个黑点相连且有 个白点 ,白点内任意连边 。
通过 复杂度预处理出来即可。
然后就没了。
#include <bits/stdc++.h> | |
#define pb push_back | |
using namespace std; | |
const int N = 110; | |
const int mod = 1e9 + 7; | |
int T, n, m; | |
int g[N], pw[N * N], h[N][N]; | |
int C[N][N], f[N][N]; | |
inline int add(int x) {return x >= mod ? x - mod : x;} | |
inline int sub(int x) {return x < 0 ? x + mod : x;} | |
inline int mul(int x, int y) {return 1ll * x * y % mod;} | |
inline int qpow(int a, int b){ | |
int res = 1; | |
while(b){ | |
if(b & 1) res = mul(res, a); | |
a = mul(a, a), b >>= 1; | |
} | |
return res; | |
} | |
inline void init(int n){ | |
for(int i = 0; i <= n; ++i) C[i][0] = 1; | |
for(int i = 1; i <= n; ++i) | |
for(int j = 1; j <= i; ++j) C[i][j] = add(C[i - 1][j - 1] + C[i - 1][j]); | |
pw[0] = 1; | |
for(int i = 1; i <= n * n; ++i) pw[i] = add(pw[i - 1] + pw[i - 1]); | |
for(int i = 1; i <= n; ++i){ | |
for(int j = 1; j < i; ++j) g[i] = add(g[i] + mul(g[j], mul(pw[(i - j) * (i - j - 1) / 2], C[i - 1][j - 1]))); | |
g[i] = sub(pw[i * (i - 1) / 2] - g[i]); | |
} | |
for(int i = 1; i <= n; ++i) | |
for(int j = 0; j <= n; ++j) | |
h[i][j] = mul(g[i], mul(qpow(pw[i] - 1, j), pw[j * (j - 1) / 2])); | |
f[0][0] = 1; | |
for(int i = 1; i <= n; ++i) | |
for(int j = 1; j <= i; ++j) | |
f[0][i] = add(f[0][i] + mul(C[i - 1][j - 1], f[0][i - j])); | |
for(int a = 1; a <= n; ++a) | |
for(int b = 0; b <= n - a; ++b) | |
for(int i = 1; i <= a; ++i) | |
for(int j = 0; j <= b; ++j){ | |
f[a][b] = add(f[a][b] + mul(mul(f[a - i][b - j], C[a - 1][i - 1]), mul(C[b][j], h[i][j]))); | |
} | |
} | |
signed main(){ | |
#ifndef ONLINE_JUDGE | |
freopen("test.in", "r", stdin); | |
freopen("test.out", "w", stdout); | |
#endif | |
ios :: sync_with_stdio(false); | |
init(N - 10); | |
cin >> T; | |
while(T--){ | |
int n, m; cin >> n >> m; | |
cout << f[n - m][m] << '\n'; | |
} | |
return 0; | |
} |
# C. 文本串的价值(value)
把 AC 自动机建出来。
先说暴力 ,设 表示匹配到文本串 的第 个字符,在 图上的 号点,当前填了状态为 的字符。
转移时判断 的下一位是不是问号,是的话枚举字符,不是的话直接跳。
注意:你要记录模式串的贡献时要在 trie 图上做链和(根到当前点的链和)。
不是子树和!!!不是子树和!!!不是子树和!!!
然后你就能拿到 30 分的好成绩。
观察到如果 不是问号,转移是固定的,我们还要枚举实在是浪费。
并且最多只有 14 个问号。
所以我们预处理出来 表示在第 个问号且当前在 trie 图上 号点,依次跳每个字符到 个问号之前会到达哪个点,且中间会得到多少收益。
这样一来,我们转移的时候可以先枚举状态 ,计算出当前是在哪两个问号之间跳,然后枚举转移字符 ,当前位置 就可以转移了。
时间复杂度 ,有个字符集的常熟。
感觉正解比暴力好写。
#include <bits/stdc++.h> | |
#define pb push_back | |
#define fi first | |
#define se second | |
using namespace std; | |
const int N = 4e5 + 10; | |
int k, n; | |
char s[N], t[N]; | |
int ch[N][15], tot, num[N]; | |
inline void insert(char *s, int x){ | |
int len = strlen(s), u = 0; | |
for(int i = 0; i < len; ++i){ | |
int c = s[i] - 'a'; | |
if(!ch[u][c]) ch[u][c] = ++tot; | |
u = ch[u][c]; | |
} | |
num[u] += x; | |
} | |
int fail[N]; | |
inline void build(){ | |
queue <int> q; | |
for(int i = 0; i < 14; ++i) | |
if(ch[0][i]) q.push(ch[0][i]); | |
while(!q.empty()){ | |
int x = q.front(); q.pop(); | |
for(int i = 0; i < 14; ++i){ | |
if(ch[x][i]) fail[ch[x][i]] = ch[fail[x]][i], q.push(ch[x][i]); | |
else ch[x][i] = ch[fail[x]][i]; | |
} | |
} | |
} | |
typedef pair<int, int> P; | |
int f[1010][(1 << 14) + 5], g[1010][(1 << 14) + 5]; | |
P dp[1010][1010]; | |
int p[N], cnt; | |
inline void Max(int &x, int y) {x = x > y ? x : y;} | |
vector <int> G[N]; | |
inline void dfs(int x){ | |
for(auto y : G[x]) num[y] += num[x], dfs(y); | |
} | |
signed main(){ | |
// #ifndef ONLINE_JUDGE | |
// freopen("test.in", "r", stdin); | |
// freopen("test.out", "w", stdout); | |
// #endif | |
cin >> k; | |
for(int i = 1, x; i <= k; ++i) | |
cin >> t >> x, insert(t, x); | |
build(); | |
for(int i = 1; i <= tot; ++i) G[fail[i]].pb(i); | |
dfs(0); | |
cin >> (s + 1), n = strlen(s + 1); | |
for(int i = 1; i <= n; ++i) | |
if(s[i] == '?') p[++cnt] = i; | |
p[cnt + 1] = n + 1; | |
for(int i = 0; i <= cnt; ++i) | |
for(int j = 0; j <= tot; ++j){ | |
int x = j, v = 0; | |
for(int l = p[i] + 1; l < p[i + 1]; ++l) x = ch[x][s[l] - 'a'], v += num[x]; | |
dp[i][j] = {x, v}; | |
} | |
//f ([i])[j][s]: s 第 i 个字符,树上 j 点,状态 S | |
memset(f, -0x3f, sizeof(f)); | |
f[dp[0][0].fi][0] = dp[0][0].se; | |
int ans = -1e9; | |
for(int S = 0; S < (1 << 14); ++S){ | |
int c1 = __builtin_popcount(S); | |
if(c1 == cnt) for(int i = 0; i <= tot; ++i) Max(ans, f[i][S]); | |
if(c1 >= cnt) continue; | |
for(int c = 0; c < 14; ++c){ | |
if(S & (1 << c)) continue; | |
for(int i = 0; i <= tot; ++i) | |
Max(f[dp[c1 + 1][ch[i][c]].fi][S | (1 << c)], f[i][S] + dp[c1 + 1][ch[i][c]].se + num[ch[i][c]]); | |
} | |
} | |
cout << ans << '\n'; | |
// 暴力 | |
// for(int i = 0; i < n; ++i){ | |
// for(int j = 0; j <= tot; ++j){ | |
// memcpy(g[j], f[j], sizeof(f[j])); | |
// memset(f[j], -0x3f, sizeof(f[j])); | |
// } | |
// for(int j = 0; j <= tot; ++j){ | |
// if(s[i + 1] != '?'){ | |
// int k = ch[j][s[i + 1] - 'a']; | |
// for(int S = 0; S < (1 << 14); ++S){ | |
// if(g[j][S] >= -1e9) Max(f[k][S], g[j][S] + num[k]); | |
// } | |
// }else{ | |
// for(int c = 0; c < 14; ++c){ | |
// for(int S = 0; S < (1 << 14); ++S){ | |
// if(g[j][S] >= -1e9 && !((S >> c) & 1)){ | |
// int k = ch[j][c]; | |
// Max(f[k][S | (1 << c)], g[j][S] + num[k]); | |
// } | |
// } | |
// } | |
// } | |
// } | |
// } | |
// for(int j = 0; j <= tot; ++j) | |
// for(int S = 0; S < (1 << 14); ++S) | |
// ans = max(ans, f[j][S]); | |
// cout << ans << '\n'; | |
return 0; | |
} |
# D. 排列(permutation)
没看,不会(
自己看题解吧。