临时起意加塞
T1 Hunte
T2 Defence
T3 Connect
从今天开始我要好好写比赛总结
赛时思路
今天由于没有特别睡醒,所以考试前一个小时状态不佳。
T1 看见 45pts 可做,决定写状压 dp。写了一个小时没写出来,然后改为去写状压 + 记搜,一下子就写出来了。
T2 上有策略的失误。T2 是线段树合并简单题。但是由于时间安排上的不合理在最后半个小时时才开始看 T2,而且在接下来的 20 分钟中,手玩样例错了两次(我也是服了我了。导致最后推出结论后暴力都没有打完。就很屎。
T3 的话,明智的,打了一个伪的最大生成树走人,拿 10pts。
总结
- 要解决打瞌睡的问题(很重要)。
- 要合理规划时间(很重要)。把每道题都大概想一点再去码代码。
题解
T1 Hunte
我们只考虑三个猎人 \(1\)、\(i\)、\(x\),然后我们不难发现 \(i\) 在 \(1\) 噶之前噶掉的概率就是 \(\frac{w_i}{w_1+w_i}\),也就是 \(x\) 在 \(i\) 和 \(1\) 中选择噶 \(i\) 的概率。那么对于每个除了 \(i\) 和 \(1\) 的猎人 \(x\) 都是如此。所以 \(\text{ans}=\sum_{i=2}^n \frac{w_i}{w_1+w_i}\)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MOD = 998244353;
const int N = 1e5 + 10;
int n, w[N];
int fpow(int a, int x) {
a %= MOD;
int ans = 1;
while (x) {
if (x & 1) ans = ans * a % MOD;
a = a * a % MOD;
x >>= 1;
}
return ans;
}
signed main() {
freopen("hunter.in", "r", stdin);
freopen("hunter.out", "w", stdout);
scanf("%lld", &n);
for (int i = 1; i <= n; i++) scanf("%lld", &w[i]);
int ans = 1;
for (int i = 2; i <= n; i++) {
ans = (ans + w[i] * fpow(w[1] + w[i], MOD - 2) % MOD) % MOD;
}
printf("%lld\n", ans);
return 0;
}
T2 Defence
T2 我们从子节点往父亲节点累加信息(在处理完子节点的信息后不要丢掉,通过修改操作转化为父亲节点的信息),这使我们不难想到线段树。又发现要考虑合并不同儿子的信息,并且要与父亲原有的信息合并,所以用线段树合并就行了。
#include <bits/stdc++.h>
using namespace std;
int read() {
int x = 0; char ch = getchar();
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x;
}
const int N = 1e5 + 10;
int n, m, qq;
vector<int> G[N];
int ans[N];
int tot, root[N];
struct node {
int lc, rc, s, l, r;
};
node tree[40 * N];
#define lc tree[p].lc
#define rc tree[p].rc
void pushup(int p, int l, int r) {
if (!lc && !rc) tree[p].s = tree[p].l = tree[p].r = r - l + 1;
int mid = l + r >> 1;
if (!rc) {
tree[p].s = max(tree[lc].s, tree[lc].r + r - mid);
tree[p].l = tree[lc].l;
tree[p].r = tree[lc].r + r - mid;
} else if (!lc) {
tree[p].s = max(tree[rc].s, tree[rc].l + mid - l + 1);
tree[p].r = tree[rc].r;
tree[p].l = tree[rc].l + mid - l + 1;
} else {
tree[p].s = max(tree[lc].r + tree[rc].l, max(tree[lc].s, tree[rc].s));
tree[p].l = tree[lc].l;
tree[p].r = tree[rc].r;
}
}
void update(int &p, int l, int r, int x) {
if (!p) p = ++tot;
if (l == r) {
tree[p].l = tree[p].r = tree[p].s = 0;
return;
}
int mid = l + r >> 1;
if (x <= mid) update(lc, l, mid, x);
else update(rc, mid + 1, r, x);
pushup(p, l, r);
}
#undef lc
#undef rc
int merge(int a, int b, int l, int r) {
if (!a || !b) return a + b;
if (l == r) {
tree[a].l = tree[a].r = tree[a].s = 0;
return a;
}
int mid = l + r >> 1;
tree[a].lc = merge(tree[a].lc, tree[b].lc, l, mid);
tree[a].rc = merge(tree[a].rc, tree[b].rc, mid + 1, r);
pushup(a, l, r);
return a;
}
void dfs(int x, int fa) {
for (int y : G[x]) {
if (y != fa) {
dfs(y, x);
root[x] = merge(root[x], root[y], 1, m);
}
}
ans[x] = max(tree[root[x]].s, tree[root[x]].l + tree[root[x]].r);
if (!root[x] || tree[root[x]].s == m) ans[x] = -1;
}
int main() {
freopen("defence.in", "r", stdin);
freopen("defence.out", "w", stdout);
n = read(), m = read(), qq = read();
for (int i = 1; i < n; i++) {
int x = read(), y = read();
G[x].push_back(y);
G[y].push_back(x);
}
for (int i = 1; i <= qq; i++) {
int u = read(), p = read();
update(root[u], 1, m, p);
}
dfs(1, 0);
for (int i = 1; i <= n; i++) {
printf("%d\n", ans[i]);
}
return 0;
}
T3 Connect
这是一道状压 dp。
设 \(f_{st,i}\) 表示现在选点的状态是 \(st\),路径的结尾是 \(i\) 的边权和的最大值。
然后有两种转移:
- 在路径的末尾加一个点。
- 加进去一坨点,且加进去的这么多点,只能与路径上的一个点连边。
至于那什么转移,第一种显然就是这条边的边权,第二种则是加进去的点集内的边权和加上与路径上一个点连边的边权和。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 20, M = 32768 + 10;
int n, m, mp[N][N];
vector<int> G[N];
int rk[M];
int val[M], num[M][N];
int f[M][N];
void print(int x) {
for (int i = 1; i <= n; i++) cout << ((x >> i - 1) & 1);
}
signed main() {
freopen("connect.in", "r", stdin);
freopen("connect.out", "w", stdout);
scanf("%lld%lld", &n, &m);
int sum = 0;
for (int i = 1; i <= m; i++) {
int x, y, w;
scanf("%lld%lld%lld", &x, &y, &w);
G[x].push_back(y);
G[y].push_back(x);
mp[x][y] = mp[y][x] = w;
sum += w;
}
for (int i = 1; i <= n; i++) rk[1 << i - 1] = i;
for (int st = 0; st < (1 << n); st++)
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
if (((st >> i - 1) & 1) && ((st >> j - 1) & 1))
val[st] += mp[i][j];
for (int st = 0; st < (1 << n); st++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (!((st >> i - 1) & 1) && ((st >> j - 1) & 1))
num[st][i] += mp[j][i];
for (int st = 0; st < (1 << n); st++)
for (int i = 1; i <= n; i++) f[st][i] = -INF;
f[1][1] = 0;
for (int st = 0; st < (1 << n); st++) {
for (int i = 1; i <= n; i++) {
if (!((st >> i - 1) & 1)) continue;
for (int j : G[i]) {
if ((st >> j - 1) & 1) continue;
f[st | (1 << j - 1)][j] = max(f[st | (1 << j - 1)][j], f[st][i] + mp[i][j]);
}
int s = (~st) & ((1 << n) - 1);
for (int ss = s; ss; ss = (ss - 1) & s) {
for (int sss = st; sss; sss -= (sss & -sss)) {
int j = rk[sss & -sss];
f[st | ss][i] = max(f[st | ss][i], f[st][i] + num[ss][j] + val[ss]);
}
}
}
}
printf("%lld\n", sum - f[(1 << n) - 1][n]);
return 0;
}
标签:总结,比赛,int,root,tree,st,rc,加塞,lc
From: https://www.cnblogs.com/zsk123qwq/p/18464880