Educational Codeforces Round 125
Educational Codeforces Round 125
A,B,C 比较没意思
D - For Gamers. By Gamers.
有 \(n\) 种军队可以雇佣,初始有 \(C\) 元钱。
有 \(m\) 只怪物需要击败,每次都有 \(C\) 元,但每次只能雇佣一种军队。
军队有三个参数:\(c_i,d_i,h_i\),分别表示对于一个单位这种士兵,他的雇佣价钱、攻击力、血量。
怪物有两个参数:\(D_j,H_j\),分别表示该怪物的攻击力、血量。
怪物会一直共计一个单位士兵,要求不能有士兵死亡,求最少要花费的钱数,如果 \(>C\) 输出无解。
注意攻击伤害是连续的,不是离散的。
实际上,设 \(k_{i,j}\) 表示第 \(i\) 种士兵杀死第 \(j\) 只怪物的最小雇佣人数,显然就是满足:
移项得到:
那么,用第 \(i\) 种士兵杀死第 \(j\) 只怪物的代价就是 \(c_ik_{i,j}\)。
发现这是一个分段函数,下取整很不好维护,于是就有点卡住了。
中间奇思妙想了一段用李超树维护分段函数单点最值,但是发现离散的分段函数不能利用连续函数一样的性质。
然后发现有个条件 \(C\leq 10^6\) 没有用到,比较启发的想到可以更暴力地维护这个过程。
直接记录 \(\text{Mx}_i\) 表示用 \(i\) 元钱能杀死的最大 \(H_jD_j\) 值,对于每种 \(c_i\) 记录最大的 \(h_id_i\),然后调和级数的复杂度暴力刷新。
最后取个前缀 \(\max\),每次二分找到答案即可。
#include<bits/stdc++.h>
typedef long long LL;
#define rep(i, s, t) for(int i = (s); i <= (t); i ++)
#define per(i, s, t) for(int i = (s); i >= (t); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;
const int N = 3e5 + 10;
const int M = 1e6 + 10;
int n, m, C; LL x[M], Mx[M];
LL read() {
LL x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
int main() {
n = read(), C = read();
rep(o, 1, n) {
LL c = read(), d = read(), h = read();
x[c] = max(x[c], d * h);
}
rep(c, 1, C) {
LL d = x[c]; if(! d) continue;
for(int i = c; i <= C; i += c)
Mx[i] = max(Mx[i], d * (i / c) - 1);
}
rep(i, 1, C) Mx[i] = max(Mx[i], Mx[i - 1]);
Mx[C + 1] = 2e18;
m = read();
rep(o, 1, m) {
LL D = read(), H = read();
D *= H;
int ans = lower_bound(Mx + 1, Mx + C + 2, D) - Mx;
if(ans > C) puts("-1");
else printf("%d\n", ans);
}
return 0;
}
E - Star MST
求美丽的 \(n\) 个点的无向完全图个数。
一张连通无向图是美丽的,当且仅当与 \(1\) 相连的边权和 = 全局最小生成树权值和。
且每条边的权值 \(\in[1,K]\)。
发现实际上是 \(1\) 放在上面,下面一排 \(n-1\) 个点,下面的边满足均不小于端点与 \(1\) 的连边大小。
设 \(f(i,j)\) 表示下面一排 \(i\) 个点,每个点向 \(1\) 的连边值域为 \([1,j]\) 的方案数。
按照 \(j\) 递增的顺序转移,不难得到:
其中 \(\text{num}(i,k)\) 表示新增的边数,DP 是根据新增几条大小恰好为 \(k\) 的节点进行划分的。
显然有 \(\displaystyle \text{num}(i,k)=\frac{(i-k)(i-k-1)}{2}+k(i-k)\),而这些边的值域都为 \([j,K]\),转移就比较显然了。
由于顺序相关,不妨先钦定 \(n-1\) 个点严格按照顺序排序,最后乘上 \((n-1)!\),转移时则需要乘上阶乘的逆元。
当然中途乘上组合数也行,不够优美,实际上乘上逆元就是把多重集的组合分子分母分开维护,这些细节都是平凡的。
#include<bits/stdc++.h>
typedef long long LL;
#define rep(i, s, t) for(int i = (s); i <= (t); i ++)
#define per(i, s, t) for(int i = (s); i >= (t); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;
const int N = 300;
const int P = 998244353;
int n, k, f[N], fac[N], inv[N];
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
int Pow(int a, int b) {
int s = 1;
for(; b; b >>= 1, a = 1LL * a * a % P)
if(b & 1) s = 1LL * s * a % P;
return s;
}
void Pl(int &x, int y) {x += y; if(x >= P) x -= P;}
int main() {
n = read(), k = read();
fac[0] = inv[0] = 1;
rep(i, 1, n) fac[i] = 1LL * i * fac[i - 1] % P;
inv[n] = Pow(fac[n], P - 2);
per(i, n - 1, 1) inv[i] = 1LL * (i + 1) * inv[i + 1] % P;
f[0] = 1;
rep(t, 1, k)
per(i, n - 1, 0) rep(j, 0, i - 1)
Pl(f[i], 1LL * f[j] * Pow(k - t + 1, (1LL * (i - j) * j % P + 1LL * (i - j) * (i - j - 1) / 2 % P) % P) % P * inv[i - j] % P);
printf("%d\n", 1LL * f[n - 1] * fac[n - 1] % P);
return 0;
}
F - Words on Tree
给定 \(m\) 条树链信息 \((u,v,s)\),其中 \(s\) 是与 \((u,v)\) 路径长度等长的字符串。
要求给每个节点确认字符,使得最终对于每个条件,要么 \((u,v)=s\),要么 \((v,u)=s\)。
或者输出无解。
或成为最简单的 F 题,个人认为思维难度不及 D(
其实就是 2-SAT 裸题嘛,每个节点只有至多两种取值。
实现的时候不要傻乎乎的仅在每个节点确定状态,然后讨论来讨论去的。
可以直接把询问也加入状态,根据询问间的矛盾建边,每个节点第一次遍历确定它的 \(0/1\) 状态,代码写起来十分小清新。
#include<bits/stdc++.h>
typedef long long LL;
#define rep(i, s, t) for(int i = (s); i <= (t); i ++)
#define per(i, s, t) for(int i = (s); i >= (t); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;
const int N = 4e5 + 10;
const int M = N << 2;
int n, q, cnt;
char str[N];
vector<int> T[M];
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
int fa[N], dep[N];
void dfs(int u, int Fa) {
fa[u] = Fa, dep[u] = dep[Fa] + 1;
for(int v : T[u]) if(v != Fa) dfs(v, u);
}
int id[M][2], frm[N], bac[N], arc[N];
char c[N][2];
vector<int> G[M];
void Add(int u, int ou, int v, int ov) {
G[id[u][ou]].push_back(id[v][ov]);
G[id[v][ov ^ 1]].push_back(id[u][ou ^ 1]);
}
void Cov(int u, int v, char *str, int qid) {
frm[0] = bac[0] = 0;
while(dep[u] > dep[v]) frm[++ frm[0]] = u, u = fa[u];
while(dep[v] > dep[u]) bac[++ bac[0]] = v, v = fa[v];
while(u != v)
frm[++ frm[0]] = u, u = fa[u],
bac[++ bac[0]] = v, v = fa[v];
frm[++ frm[0]] = u;
int t = 0;
rep(i, 1, frm[0]) arc[++ t] = frm[i];
per(i, bac[0], 1) arc[++ t] = bac[i];
rep(i, 1, t) {int u = arc[i]; if(c[u][0] == '#') c[u][0] = str[i], c[u][1] = str[t - i + 1];}
rep(i, 1, t) {
int u = arc[i];
rep(o, 0, 1) {
if(c[u][o] != str[i]) Add(u, o, qid, 1);
if(c[u][o] != str[t - i + 1]) Add(u, o, qid, 0);
}
}
}
int num, tot, top, dfn[M], low[M], stk[M], col[M];
void tarjan(int u) {
dfn[u] = low[u] = ++ num;
stk[++ top] = u;
for(int v : G[u]) {
if(! dfn[v])
tarjan(v), low[u] = min(low[u], low[v]);
else if(! col[v])
low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u]) {
int v; tot ++;
do {
v = stk[top --];
col[v] = tot;
} while(v != u);
}
}
int main() {
n = read(), q = read();
rep(i, 1, n) c[i][0] = c[i][1] = '#';
rep(i, 1, n + q) id[i][0] = ++ cnt, id[i][1] = ++ cnt;
rep(i, 2, n) {
int u = read(), v = read();
T[u].push_back(v);
T[v].push_back(u);
}
dfs(1, 0);
rep(i, 1, q) {
int u = read(), v = read();
scanf("%s", str + 1);
Cov(u, v, str, i + n);
}
rep(i, 1, cnt) if(! dfn[i]) tarjan(i);
rep(i, 1, n + q) if(col[id[i][0]] == col[id[i][1]]) {puts("NO"); return 0;}
puts("YES");
rep(i, 1, n) {
char ans = c[i][col[id[i][0]] > col[id[i][1]]];
if(ans == '#') ans = 'a'; putchar(ans);
} puts("");
return 0;
}