《如 何 速 通 一 套 题》6.0
邮寄
开场秒掉 A。
然后陷入了无尽的 BCD 循环中......
C 推了我好久,然后没推出来......
A 集合
考虑每一个子集和的贡献。设 \(dp_i\) 表示子集和为 \(i\) 的方案数,转移易推。
答案是
但是 \(n\) 有 \(200\) 会原地起飞,没事,我们可以取模......
注意!!
对于指数上的数,千万不要 \(\bmod 998244353\),要 \(\bmod 998244352\)!
根据费马小定理,\(a^{\color{red}{p - 1}} \equiv 1 \pmod p\)!
然后就可以了......
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int kMod = 998244353;
int n, dp[40040], lm, ans = 1;
int qpow(int x, int y) {
int rs = 1;
for(; y; y >>= 1) {
if(y & 1) {
rs = rs * x % kMod;
}
x = x * x % kMod;
}
return rs;
}
signed main() {
freopen("set.in", "r", stdin);
freopen("set.out", "w", stdout);
cin >> n;
lm = n * (n + 1) / 2;
dp[0] = 1;
for(int i = 1; i <= n; i++) {
for(int j = lm - i; j >= 0; j--) {
dp[j + i] = (dp[j + i] + dp[j]) % (kMod - 1); // -1 is key
}
}
for(int i = 1; i <= lm; i++) {
ans = ans * qpow(i, dp[i]) % kMod;
}
cout << ans << '\n';
return 0;
}
B 出租
考虑一个区间 \([l, r]\) 里面的人。
可以写出如下方程:
稍微变形一下:
维护左边的最大子段和,然后就可以直接判断了。
如何动态求解最大子段和(Spoiler Alert!)
动态维护,果断祭出线段树。
维护如下四个信息:
\(pm, sm, as, fs\) 代表(非空)前缀最大值,后缀最大值,最大子段和,区间和,
合并如下:
\(pm = \max(pm_{ls}, fs_{ls} + pm_{rs})\)
\(sm = \max(sm_{rs}, fs_{rs} + sm_{ls})\)
\(as = \max(as_{ls}, as_{rs}, sm_{ls} + pm_{rs})\)
\(fs = fs_{ls} + fs_{rs}\)
由于只有单点修改,所以不需要多少维护。
#include <bits/stdc++.h>
#define int long long
using namespace std;
struct dat {
int pm, sm, as, fs;
dat(int s = -1e10, int p = -1e10, int d = -1e10, int f = -1e10) : pm(), sm(), as(), fs() {
pm = s, sm = p, as = d, fs = f;
}
dat operator ^(const dat &b) const {
return dat(max(pm, fs + b.pm), max(sm + b.fs, b.sm), max({as, b.as, sm + b.pm}), fs + b.fs);
}
}d[2000020];
int n, m, k, l, arr[500050], x, y;
void build(int id = 1, int l = 1, int r = n) {
if(l == r) {
d[id] = {arr[l], arr[l], arr[l], arr[l]};
return ;
}
int mid = (l + r) >> 1;
build(id * 2, l, mid);
build(id * 2 + 1, mid + 1, r);
d[id] = d[id * 2] ^ d[id * 2 + 1];
}
void modify(int x, int v, int id = 1, int l = 1, int r = n) {
if(l == r) {
arr[l] = v;
d[id] = {arr[l], arr[l], arr[l], arr[l]};
return ;
}
int mid = (l + r) >> 1;
if(x <= mid) {
modify(x, v, id * 2, l, mid);
}else {
modify(x, v, id * 2 + 1, mid + 1, r);
}
d[id] = d[id * 2] ^ d[id * 2 + 1];
}
dat query(int ql, int qr, int id = 1, int l = 1, int r = n) {
if(r < ql || qr < l) {
return dat();
}
if(ql <= l && r <= qr) {
return d[id];
}
int mid = (l + r) >> 1;
dat a = query(ql, qr, id * 2, l, mid), b = query(ql, qr, id * 2 + 1, mid + 1, r);
return a ^ b;
}
signed main() {
freopen("hire.in", "r", stdin);
freopen("hire.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m >> k >> l;
for(int i = 1; i <= n; i++) {
arr[i] -= k;
}
build();
for(; m--; ) {
cin >> x >> y;
modify(x, arr[x] + y);
dat tmp = query(1, n);
cout << (tmp.as > k * l? "NO" : "YES") << '\n';
}
return 0;
}
C 连通块
观察到我们只需要处理那些“不共戴天”的节点就可以了。
然后就自然可以 dp 了。
#include <bits/stdc++.h>
#define int ll
using namespace std;
using ll = long long;
struct node {
int u, v;
}cnd[22];
int n, m, val[100010], l, bg[44][44], tot, id[100010], dp[100010][44], ans;
vector<int> ch[100010];
void DFS(int x = 1) {
for(int i = 0; i <= tot; i++) {
dp[x][i] = -1e16;
}
dp[x][id[x]] = val[x];
for(auto i : ch[x]) {
DFS(i);
int mx = -1e16;
for(int j = 0; j <= tot; j++) {
if(!bg[j][id[i]]) {
mx = max(mx, dp[x][j]);
}
}
for(int j = 0; j <= tot; j++) {
dp[x][j] = max(dp[x][j], dp[i][j] + mx);
}
}
for(int j = 0; j <= tot; j++) {
ans = max(ans, dp[x][j]);
}
}
signed main() {
freopen("block.in", "r", stdin);
freopen("block.out", "w", stdout);
cin >> n >> m;
for(int i = 1; i <= n; i++) {
cin >> val[i];
}
for(int i = 1; i <= n; i++) {
cin >> l;
ch[i].resize(l);
for(auto &j : ch[i]) {
cin >> j;
}
}
ans = -1e16;
/*
for(int i = 1; i <= n; i++) {
ans = max(ans, val[i]);
}
if(ans <= 0) {
cout << ans << '\n';
return 0;
}
//*/
for(int i = 1; i <= m; i++) {
cin >> cnd[i].u >> cnd[i].v;
if(!id[cnd[i].u]) {
id[cnd[i].u] = ++tot;
}
if(!id[cnd[i].v]) {
id[cnd[i].v] = ++tot;
}
bg[id[cnd[i].u]][id[cnd[i].v]] = bg[id[cnd[i].v]][id[cnd[i].u]] = 1;
}
DFS();
cout << ans << '\n';
return 0;
}
D 跳棋
我们发现把所有的单个 \(1\) 拿出来后(奇数段中的一个 \(1\) 也算单个一,但是一个段中只有一个),剩下的 \(11\) 和 \(0\) 可以随便安排。
所以设 \(dp_{i, j, k, 0/1}\) 表示考虑了前 \(i\) 个,有 \(j\) 个 \(11\),有 \(k\) 个 \(0\),末尾是否可以加一个 \(1\) 变成 \(11\)。
放 \(0\) 时:\(dp_{i, j, k, 0/1} \to dp_{i + 1, j, k + 1, 0}\)
放 \(1\) 时:\(dp_{i, j, k, 0} \to dp_{i + 1, j, k, 1}, dp_{i, j, k, 1} \to dp_{i + 1, j + 1, k, 0}\)
然后答案是
#include <bits/stdc++.h>
#define int ll
using namespace std;
using ll = long long;
const int kMod = int(1e9) + 7;
int n, fac[1010], inv[1010], ans, dp[2][550][550][2];
string s;
void init() {
fac[0] = fac[1] = 1, inv[1] = 1;
inv[0] = 1;
for(int i = 2; i <= 1000; i++) {
fac[i] = fac[i - 1] * i % kMod;
inv[i] = inv[kMod % i] * (kMod - kMod / i) % kMod;
}
for(int i = 2; i <= 1000; i++) {
inv[i] = inv[i] * inv[i - 1] % kMod;
}
}
int c(int x, int y) {
if(x < 0 || y < 0 || x < y) {
return 0;
}
return fac[x] * inv[x - y] % kMod * inv[y] % kMod;
}
signed main() {
freopen("checkers.in", "r", stdin);
freopen("checkers.out", "w", stdout);
cin >> n >> s;
n = s.size();
dp[0][0][0][0] = 1;
for(int i = 0; i < n; i++) {
for(int j = 0; j <= n; j++) {
for(int k = 0; k <= n; k++) {
if(s[i] != '1') {
dp[1][j][k + 1][0] = (dp[1][j][k + 1][0] + dp[0][j][k][0] + dp[0][j][k][1]) % kMod;
}
if(s[i] != '0') {
dp[1][j][k][1] = (dp[1][j][k][1] + dp[0][j][k][0]) % kMod;
dp[1][j + 1][k][0] = (dp[1][j + 1][k][0] + dp[0][j][k][1]) % kMod;
}
}
}
for(int j = 0; j <= n; j++) {
for(int k = 0; k <= n; k++) {
dp[0][j][k][0] = dp[1][j][k][0], dp[0][j][k][1] = dp[1][j][k][1];
dp[1][j][k][0] = dp[1][j][k][1] = 0;
}
}
}
init();
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= n; j++) {
ans = (ans + (dp[0][i][j][0] + dp[0][i][j][1]) % kMod * c(i + j, j)) % kMod;
}
}
cout << ans << '\n';
return 0;
}