10 月
10.1
P2572 [SCOI2010] 序列操作:线段树(紫)
维护每个区间的两个懒标记,\(0/1\) 的数量、左端点起 \(0/1\) 的数量、右端点起 \(0/1\) 的数量、区间内最大连续 \(0/1\) 长度即可。
点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int n, m, a[N];
struct Node {
int l, r, tag1=-1, tag2=0;
int cnt0=0, cnt1=0, l0=0, l1=0, r0=0, r1=0, m0=0, m1=0;
} seg[N<<2];
void pushup(Node &U, Node &L, Node &R) {
U.cnt0 = L.cnt0+R.cnt0, U.cnt1 = L.cnt1+R.cnt1;
U.l0 = L.cnt1?L.l0:L.l0+R.l0, U.l1 = L.cnt0?L.l1:L.l1+R.l1;
U.r0 = R.cnt1?R.r0:R.r0+L.r0, U.r1 = R.cnt0?R.r1:R.r1+L.r1;
U.m0 = max(max(L.m0, R.m0), L.r0+R.l0), U.m1 = max(max(L.m1, R.m1), L.r1+R.l1);
}
void pushup(int u) {
pushup(seg[u], seg[u<<1], seg[u<<1|1]);
}
void modify(Node &U, int op) {
if (op == 0) U.tag1=U.tag2=0, U.cnt0=U.l0=U.r0=U.m0=U.r-U.l+1, U.cnt1=U.l1=U.r1=U.m1=0;
else if (op == 1) U.tag1=1, U.tag2=0, U.cnt0=U.l0=U.r0=U.m0=0, U.cnt1=U.l1=U.r1=U.m1=U.r-U.l+1;
else U.tag2 ^= 1, swap(U.cnt0, U.cnt1), swap(U.l0, U.l1), swap(U.r0, U.r1), swap(U.m0, U.m1);
}
void pushdown(Node &U, Node &L, Node &R) {
if (U.tag1 != -1) modify(L, U.tag1), modify(R, U.tag1), U.tag1 = -1;
if (U.tag2) modify(L, 2), modify(R, 2), U.tag2 = 0;
}
void pushdown(int u) {
pushdown(seg[u], seg[u<<1], seg[u<<1|1]);
}
void build(int u, int l, int r) {
seg[u].l = l, seg[u].r = r;
if (l == r) {
if (a[l]) seg[u].cnt1 = seg[u].l1 = seg[u].r1 = seg[u].m1 = 1;
else seg[u].cnt0 = seg[u].l0 = seg[u].r0 = seg[u].m0 = 1;
return ;
}
int mid = l + r >> 1;
build(u<<1, l, mid), build(u<<1|1, mid+1, r);
pushup(u);
}
void modify(int u, int l, int r, int op) {
if (seg[u].l >= l && seg[u].r <= r) {modify(seg[u], op); return ;}
pushdown(u);
int mid = seg[u].l + seg[u].r >> 1;
if (l <= mid) modify(u<<1, l, r, op);
if (r > mid) modify(u<<1|1, l, r, op);
pushup(u);
}
Node query(int u, int l, int r) {
if (seg[u].l >= l && seg[u].r <= r) return seg[u];
pushdown(u);
int mid = seg[u].l + seg[u].r >> 1; Node res, L, R;
if (l <= mid) L = query(u<<1, l, r);
if (r > mid) R = query(u<<1|1, l, r);
pushup(res, L, R);
return res;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
build(1, 1, n);
int op, l, r;
while (m -- ) {
scanf("%d%d%d", &op, &l, &r); l ++, r ++;
if (op == 0) modify(1, l, r, 0);
else if (op == 1) modify(1, l, r, 1);
else if (op == 2) modify(1, l, r, 2);
else if (op == 3) printf("%d\n", query(1, l, r).cnt1);
else printf("%d\n", query(1, l, r).m1);
}
return 0;
}
10.2
ABC249F. Ignore Operations:堆,贪心(绿)
可以发现,进行一次 1 操作后,前面的所有操作都相当于作废了。我们考虑倒着做,维护当前删除 2 操作后得到的结果最大值 \(sum\),对于每个 1 操作我们考虑是否删除,若不删除则 \(ans=\max(ans,a_i+sum)\),否则 \(k\leftarrow k-1\)。对于每个 2 操作,若 \(a_i\ge 0\) 则一定不劣,否则我们可以将其跳过。当然,我们有可能删去 \(>k\) 次操作,所以我们用一个堆维护当前删去了哪些 2 操作,若堆的大小 \(>k\) 则不断取出堆头加到 \(sum\) 中。
点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 2e5+10;
int n, k, op[N], a[N]; ll ans = -9e18, sum;
priority_queue<int> q;
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i) scanf("%d%d", &op[i], &a[i]);
for (int i = n; i >= 1 && k >= 0; --i) {
if (op[i] == 1) ans = max(ans, sum+a[i]), k --;
else {
if (a[i] >= 0) sum += a[i];
else q.push(a[i]);
}
while ((int)q.size() > k) sum += q.top(), q.pop();
}
ans = max(ans, sum);
printf("%lld\n", ans);
return 0;
}
SP13015. CNTPRIME - Counting Primes:珂朵莉树,质数筛(黄)
两个板子。
点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
using namespace std;
const int N = 1e6+10;
int T, n, m, a[N];
int idx, prime[N]; bool st[N];
struct Node {
int l, r; mutable int v;
Node (int l, int r = 0, int v = 0) : l(l), r(r), v(v) {}
bool operator < (const Node &T) const {
return l < T.l;
}
};
typedef set<Node>::iterator iter;
set<Node> s;
iter split(int pos) {
iter it = s.lower_bound(Node(pos));
if (it != s.end() && it->l == pos) return it;
it --;
if (it->r < pos) return s.end();
int l = it->l, r = it->r, v = it->v;
s.erase(it), s.insert(Node(l, pos-1, v));
return s.insert(Node(pos, r, v)).first;
}
void assign(int l, int r, int v) {
iter itr = split(r+1), itl = split(l);
s.erase(itl, itr), s.insert(Node(l, r, v));
}
int query(int l, int r) {
int cnt = 0; iter itr = split(r+1), itl = split(l);
for (iter it = itl; it != itr; ++it) if (!st[it->v]) cnt += it->r-it->l+1;
return cnt;
}
void get_prime() {
st[0] = st[1] = 1;
for (int i = 2; i <= 1000000; ++i) {
if (!st[i]) prime[++ idx] = i;
for (int j = 1; j <= idx && prime[j] <= 1000000/i; ++j) {
st[i*prime[j]] = 1;
if (i % prime[j] == 0) break;
}
}
}
void solve() {
scanf("%d%d", &n, &m); s.clear();
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), s.insert(Node(i, i, a[i]));
int op, l, r, v;
while (m -- ) {
scanf("%d%d%d", &op, &l, &r);
if (op == 0) scanf("%d", &v), assign(l, r, v);
else printf("%d\n", query(l, r));
}
}
int main() {
scanf("%d", &T); get_prime();
for (int i = 1; i <= T; ++i) printf("Case %d:\n", i), solve();
return 0;
}
P1558 色板游戏:线段树(绿)
注意到颜色数很小,可以状压表示。时间复杂度 \(O(nk\log n)\)。
点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int n, t, m;
struct Node {
int l, r, s = 0, tag = 0;
} seg[N<<2];
void pushup(Node &U, Node &L, Node &R) {
U.s = L.s | R.s;
}
void pushup(int u) {
pushup(seg[u], seg[u<<1], seg[u<<1|1]);
}
void modify(Node &U, int c) {
U.s = U.tag = c;
}
void pushdown(Node &U, Node &L, Node &R) {
if (!U.tag) return ;
modify(L, U.tag), modify(R, U.tag), U.tag = 0;
}
void pushdown(int u) {
pushdown(seg[u], seg[u<<1], seg[u<<1|1]);
}
void build(int u, int l, int r) {
seg[u].l = l, seg[u].r = r;
if (l == r) {seg[u].s = 1; return ;}
int mid = l + r >> 1;
build(u<<1, l, mid), build(u<<1|1, mid+1, r);
pushup(u);
}
void modify(int u, int l, int r, int c) {
if (seg[u].l >= l && seg[u].r <= r) {modify(seg[u], c); return ;}
pushdown(u);
int mid = seg[u].l + seg[u].r >> 1;
if (l <= mid) modify(u<<1, l, r, c);
if (r > mid) modify(u<<1|1, l, r, c);
pushup(u);
}
int query(int u, int l, int r) {
if (seg[u].l >= l && seg[u].r <= r) return seg[u].s;
pushdown(u);
int mid = seg[u].l + seg[u].r >> 1; int res = 0;
if (l <= mid) res |= query(u<<1, l, r);
if (r > mid) res |= query(u<<1|1, l, r);
return res;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> t >> m;
build(1, 1, n);
char op; int a, b, c;
while (m -- ) {
cin >> op >> a >> b; if (a > b) swap(a, b);
if (op == 'C') cin >> c, modify(1, a, b, 1<<c-1);
else {
int s = query(1, a, b), cnt = 0;
for (int i = 0; i < t; ++i) if ((s >> i) & 1) cnt ++;
cout << cnt << '\n';
}
}
return 0;
}
P3740 [HAOI2014] 贴海报:线段树,珂朵莉树(绿)
珂朵莉树裸题。
点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
using namespace std;
const int N = 1010;
int n, m, idx; bool nums[N];
struct Node {
int l, r; mutable int v;
Node (int l, int r = 0, int v = 0) : l(l), r(r), v(v) {}
bool operator < (const Node &T) const {
return l < T.l;
}
};
typedef set<Node>::iterator iter;
set<Node> s;
iter split(int pos) {
iter it = s.lower_bound(Node(pos));
if (it != s.end() && it->l == pos) return it;
it --;
if (it->r < pos) return s.end();
int l = it->l, r = it->r, v = it->v;
s.erase(it), s.insert(Node(l, pos-1, v));
return s.insert(Node(pos, r, v)).first;
}
void assign(int l, int r, int v) {
iter itr = split(r+1), itl = split(l);
s.erase(itl, itr), s.insert(Node(l, r, v));
}
int count(int l, int r) {
int cnt = 0; iter itr = split(r+1), itl = split(l); nums[0] = 1;
for (iter it = itl; it != itr; ++it) if (!nums[it->v]) nums[it->v] = 1, cnt ++;
return cnt;
}
int main() {
scanf("%d%d", &n, &m);
s.insert(Node(1, n, 0));
while (m -- ) {
int l, r; scanf("%d%d", &l, &r);
assign(l, r, ++ idx);
}
printf("%d\n", count(1, n));
return 0;
}
P9117 [春季测试 2023] 涂色游戏:模拟(橙)
对于每一行每一列,记录其最后一次修改的时间和颜色。对于点 \((i,j)\),取第 \(i\) 行和第 \(j\) 列中最后一次修改时间最晚的颜色。
点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef pair<int, int> pii;
const int N = 1e5+10;
int T, n, m, q;
pii row[N], col[N];
void solve() {
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; ++i) row[i] = {0, 0};
for (int i = 1; i <= m; ++i) col[i] = {0, 0};
int op, x, c;
for (int i = 1; i <= q; ++i) {
scanf("%d%d%d", &op, &x, &c);
if (!op) row[x] = {i, c};
else col[x] = {i, c};
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j)
printf("%d ", max(row[i], col[j]).second);
puts("");
}
}
int main() {
scanf("%d", &T);
while (T --) solve();
return 0;
}
P8512 [Ynoi Easy Round 2021] TEST_152:珂朵莉树,树状数组(紫)
区间覆盖?珂朵莉树!
由于一次操作至多增加 \(2\) 个区间,所以最终的区间数是 \(O(n)\) 的。即珂朵莉树维护的时间复杂度是 \(O(n\log n)\) 的。我们可以先将所有询问按右端点从小到大排序,记录每个区间产生的时间,删去这个区间时,我们用树状数组减去这个区间对后面时间点的贡献;插入一个区间时,我们用树状数组加上这个区间对后面时间点的贡献。
点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
using namespace std;
typedef long long ll;
const int N = 5e5+10;
int n, m, q; ll c[N], ans[N];
int lowbit(int x) {
return x & -x;
}
void add(int x, ll k) {
for (int i = x; i <= n; i += lowbit(i)) c[i] += k;
}
ll query(int x) {
ll res = 0;
for (int i = x; i >= 1; i -= lowbit(i)) res += c[i];
return res;
}
struct Operation {
int l, r, v;
} oper[N];
struct Query {
int l, r, id;
bool operator < (const Query &T) const {
return (r == T.r) ? l < T.l : r < T.r;
}
} qry[N];
struct Node {
int l, r, t; mutable int v;
Node (int l, int r = 0, int v = 0, int t = 0) : l(l), r(r), v(v), t(t) {}
bool operator < (const Node &T) const {
return l < T.l;
}
};
set<Node> s;
auto split(int pos) {
auto it = s.lower_bound(Node(pos));
if (it != s.end() && it->l == pos) return it;
it --;
int l = it->l, r = it->r, v = it->v, t = it->t;
s.erase(it), s.insert(Node(l, pos-1, v, t));
return s.insert(Node(pos, r, v, t)).first;
}
void assign(int l, int r, int v, int t) {
auto itr = split(r+1), itl = split(l);
for (auto it = itl; it != itr; ++it) if (it->t) add(it->t, -1ll*(it->r-it->l+1)*it->v);
s.erase(itl, itr), s.insert(Node(l, r, v, t)), add(t, 1ll*(r-l+1)*v);
}
int main() {
scanf("%d%d%d", &n, &m, &q); s.insert(Node(1, n, 0, 0));
int l, r, v, id;
for (int i = 1; i <= n; ++i) scanf("%d%d%d", &l, &r, &v), oper[i] = {l, r, v};
for (int i = 1; i <= q; ++i) scanf("%d%d", &l, &r), qry[i] = {l, r, i};
sort(qry+1, qry+q+1);
int now = 0;
for (int i = 1; i <= q; ++i) {
l = qry[i].l, r = qry[i].r, id = qry[i].id;
while (now < r) now ++, assign(oper[now].l, oper[now].r, oper[now].v, now);
ans[id] = query(r) - query(l-1);
}
for (int i = 1; i <= q; ++i) printf("%lld\n", ans[i]);
return 0;
}
P5104 红包发红包:数学,期望(绿)
容易猜到剩下 \(x\) 元时,第 \(1\) 个人期望拿到 \(\dfrac x2\) 元。则答案为 \(\dfrac{w}{2^k}\)。
点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
const int P = 1e9+7;
int w, n, k;
int power(int a, int b) {
int res = 1;
while (b) {
if (b & 1) (res *= a) %= P;
(a *= a) %= P, b >>= 1;
}
return res;
}
signed main() {
cin >> w >> n >> k;
cout << w * power(power(2, k), P-2)%P << '\n';
return 0;
}
CF472D. Design Tutorial: Inverse the Problem:最小生成树,dfs(蓝)
将这个邻接矩阵看作一张图,跑一遍最小生成树,用 dfs 判断即可。注意一些细枝末节的判断。
点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int, int> pii;
const int N = 2010;
struct Edge {
int u, v, w;
bool operator < (const Edge &T) const {
return w < T.w;
}
}; vector<Edge> e;
int n, a[N][N], p[N], dist[N][N];
vector<pii> E[N];
int find(int x) {
return p[x] == x ? p[x] : p[x] = find(p[x]);
}
void dfs(int u, int now) {
for (auto edge : E[now]) {
int v = edge.first, w = edge.second;
if (!dist[u][v] && u != v) dist[u][v] = dist[u][now]+w, dfs(u, v);
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) scanf("%d", &a[i][j]);
for (int i = 1; i <= n; ++i) {
p[i] = i;
for (int j = 1; j <= i; ++j) {
if (i == j) {if (a[i][j]) puts("NO"), exit(0);}
else if (a[i][j] != a[j][i] || a[i][j] < 1) puts("NO"), exit(0);
e.push_back({i, j, a[i][j]});
}
}
sort(e.begin(), e.end());
int now = n-1;
for (auto edge : e) {
int u = edge.u, v = edge.v, w = edge.w;
int fu = find(u), fv = find(v);
if (fu == fv) continue;
p[fu] = fv, now --, E[u].push_back({v, w}), E[v].push_back({u, w});
if (!now) break;
}
for (int i = 1; i <= n; ++i) dfs(i, i);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j)
if (a[i][j] != dist[i][j])
puts("NO"), exit(0);
}
puts("YES");
return 0;
}
ABC246G. Game on Tree 3:二分,dp(蓝)
显然答案具有单调性。
不妨设当前二分的答案为 \(x\),我们可以将所有满足 \(a_i\ge x\) 的点染为黑色,所有满足 \(a_i<x\) 的点染为白色。令 \(f_i\) 表示 B 走到 \(i\) 号点时,A 至少要进行多少次操作才能使 B 不能在以 \(i\) 为根的子树内走到任何黑点。考虑状态转移,当存在边 \((i,j)\) 且以 \(j\) 为根的子树内存在黑点可以让 B 走到时,B 就一定会走过去。所以,A 至少要进行 \(\max(\sum f_j-1,0)\) 次操作(减去 \(1\) 是因为 A 在 B 走到 \(i\) 后还可以进行一次操作),另外还要考虑 \(i\) 号点是否为黑点。
所以我们有:
\[f_i=\max\left(\sum f_j-1,0\right)+[a_i\ge x] \]时间复杂度 \(O(n\log V)\)。
点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 2e5+10;
int n, a[N], f[N];
vector<int> nums, e[N];
void dfs(int u, int fa, int x) {
int sum = 0;
for (auto v : e[u]) {
if (v == fa) continue;
dfs(v, u, x), sum += f[v];
}
f[u] = max(sum-1, 0)+(a[u] >= x);
}
int main() {
scanf("%d", &n); nums.push_back(0);
for (int i = 2; i <= n; ++i) scanf("%d", &a[i]), nums.push_back(a[i]);
for (int i = 1; i < n; ++i) {
int u, v; scanf("%d%d", &u, &v);
e[u].push_back(v), e[v].push_back(u);
}
sort(nums.begin(), nums.end());
int l = 0, r = n-1;
while (l < r) {
int mid = l + r + 1 >> 1; dfs(1, 1, nums[mid]);
if (f[1] >= 1) l = mid;
else r = mid-1;
}
printf("%d\n", nums[l]);
return 0;
}
CF875D. High Cry:单调栈,st 表,二分(紫)
用单调栈求出 \(a_i\) 左边第一个 \(\le a_i\) 的位置 \(L_i\),右边第一个 \(<a_i\) 的位置 \(R_i\)(为什么一个小于等于一个小于?因为一个闭区间和一个开区间正好拼成一个完整的区间)。二分在 \([L_i,i]\) 的区间中找到一个最小的位置 \(l\) 使得 \(a_l\vee a_{l+1}\vee \cdots \vee a_i=a_i\),在 \([i,R_i]\) 中找到一个最大的位置 \(r\) 使得 \(a_i\vee a_{i+1}\vee\cdots\vee a_{r}=a_{i}\)。则 \(a_i\) 所产生的不合法的区间即为 \((i-l+1)(r-i+1)\)。将总的区间数减去不合法的区间数量之和即可。
点击展开代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = 2e5+10;
int n, a[N], L[N], R[N], st[N][25]; ll ans;
stack<int> s;
int query(int l, int r) {
int k = log2(r-l+1);
return st[l][k] | st[r-(1<<k)+1][k];
}
int findL(int l, int r, int x) {
while (l < r) {
int mid = l + r >> 1;
if (query(mid, x) > a[x]) l = mid+1;
else r = mid;
}
return l;
}
int findR(int l, int r, int x) {
while (l < r) {
int mid = l + r + 1 >> 1;
if (query(x, mid) > a[x]) r = mid-1;
else l = mid;
}
return l;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), st[i][0] = a[i];
for (int j = 1; (1<<j) <= n; ++j) {
for (int i = 1; i+(1<<j)-1 <= n; ++i)
st[i][j] = st[i][j-1] | st[i+(1<<j-1)][j-1];
}
for (int i = 1; i <= n; ++i) {
while (s.size() && a[s.top()] <= a[i]) s.pop();
if (s.size()) L[i] = s.top()+1; else L[i] = 1;
s.push(i);
}
while (s.size()) s.pop();
for (int i = n; i >= 1; --i) {
while (s.size() && a[s.top()] < a[i]) s.pop();
if (s.size()) R[i] = s.top()-1; else R[i] = n;
s.push(i);
}
for (int i = 1; i <= n; ++i) {
int l = findL(L[i], i, i), r = findR(i, R[i], i);
ans += ((ll)i-l+1)*(r-i+1);
}
printf("%lld\n", 1ll*n*(n+1)/2-ans);
return 0;
}
10.5
CF103687L. Candy Machine:贪心(绿)
假设最终选择的集合的平均数不超过 \(k\),那么应将 \(\le k\) 的数全部选入,然后贪心选择 \(>k\) 的部分中最小的若干个数。因此将 \(n\) 个数从小到大排序后,最优解一定是一个前缀。枚举每个前缀,二分找到位置即可。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
int n, ans, a[N]; ll tot; double aver;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), tot += a[i];
aver = tot / (double)n;
sort(a+1, a+n+1);
int pos = upper_bound(a+1, a+n+1, aver) - a; ans = n-pos+1;
for (int i = n; i > 1; --i) {
tot -= a[i], aver = tot / (double)(i-1);
pos = upper_bound(a+1, a+i, aver) - a, ans = max(ans, i-pos);
}
printf("%d\n", ans);
return 0;
}
P4717 【模板】快速莫比乌斯/沃尔什变换 (FMT/FWT):FWT(紫)
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = (1<<17)+10, P = 998244353;
int n, m, a[N], b[N], A[N], B[N];
void init() {
for (int i = 0; i < n; ++i) A[i] = a[i], B[i] = b[i];
}
void FWT_OR(int f[], int op) {
for (int x = 1; x < n; x <<= 1) {
for (int i = 0; i < n; i += x<<1)
for (int j = 0; j < x; ++j)
(f[i+j+x] += f[i+j] * op) %= P;
}
}
void FWT_AND(int f[], int op) {
for (int x = 1; x < n; x <<= 1) {
for (int i = 0; i < n; i += x<<1)
for (int j = 0; j < x; ++j)
(f[i+j] += f[i+j+x] * op) %= P;
}
}
void FWT_XOR(int f[], int op) {
for (int x = 1; x < n; x <<= 1) {
for (int i = 0; i < n; i += x<<1)
for (int j = 0, f1, f2; j < x; ++j)
f1 = f[i+j], f2 = f[i+j+x], f[i+j] = 1ll * (f1+f2) * op % P, f[i+j+x] = 1ll * (f1-f2) * op % P;
}
}
void FWT_CALC() {
for (int i = 0; i < n; ++i) A[i] = 1ll * A[i] * B[i] % P;
}
void output() {
for (int i = 0; i < n; ++i) printf("%d ", (A[i]+P)%P); puts("");
}
int main() {
scanf("%d", &m); n = 1 << m;
for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
for (int i = 0; i < n; ++i) scanf("%d", &b[i]);
init(), FWT_OR(A, 1), FWT_OR(B, 1), FWT_CALC(), FWT_OR(A, -1), output();
init(), FWT_AND(A, 1), FWT_AND(B, 1), FWT_CALC(), FWT_AND(A, -1), output();
init(), FWT_XOR(A, 1), FWT_XOR(B, 1), FWT_CALC(), FWT_XOR(A, 499122177), output();
return 0;
}
10.9
ABC245D. Polynomial division:数学(黄)
其实就是高精度除法模拟。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, m, A[210], B[210], C[210];
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i <= n; ++i) scanf("%d", &A[i]);
for (int i = 0; i <= n+m; ++i) scanf("%d", &C[i]);
for (int i = m, j = n+m; i >= 0; --i, --j) {
B[i] = C[j] / A[n];
for (int k = j, t = n; k >= i; --k, --t) C[k] -= B[i] * A[t];
}
for (int i = 0; i <= m; ++i) printf("%d ", B[i]);
return 0;
}
ABC245E. Wrapping Chocolate:排序,贪心(绿)
将所有巧克力和盒子放在一起,分别以 \(x,y,type\)(若巧克力和盒子的 \(x,y\) 都相同,则盒子排在前面)为第一、二、三关键字从大到小排序。然后扫一遍所有巧克力和盒子,若当前为盒子则将它的 \(y\) 加入一个 multiset
,否则在 multiset
里找到最小的一个 \(y'\) 使得 \(y'\ge y\)。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
using namespace std;
const int N = 4e5+10;
int n, m;
multiset<int> pos;
struct Node {
int x, y, id;
bool operator < (const Node &T) const {
return (x == T.x) ? ((y == T.y) ? id > T.id : y > T.y) : x > T.x;
}
} s[N];
int main() {
scanf("%d%d", &n, &m);
int x, y;
for (int i = 1; i <= n; ++i) scanf("%d", &x), s[i].x = x;
for (int i = 1; i <= n; ++i) scanf("%d", &y), s[i].y = y, s[i].id = 1;
for (int i = 1; i <= m; ++i) scanf("%d", &x), s[n+i].x = x;
for (int i = 1; i <= m; ++i) scanf("%d", &y), s[n+i].y = y, s[n+i].id = 2;
sort(s+1, s+n+m+1);
for (int i = 1; i <= n+m; ++i) {
if (s[i].id == 2) pos.insert(s[i].y);
else {
auto now = pos.lower_bound(s[i].y);
if (now == pos.end()) puts("No"), exit(0);
pos.erase(now);
}
}
puts("Yes");
return 0;
}
ABC245F. Endless Walk:拓扑排序(绿)
显然,若一个点出度为 \(0\),则这个点显然不可能不停止地走下去,给这个点打上标记。若一个点能到达的所有点都是有标记的点,则这个点也要打上标记。最后没有被打上标记的点的数量即为答案。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 2e5+10;
int n, m, cnt; vector<int> e[N];
bool st[N]; int deg[N];
void toposort() {
queue<int> q;
for (int i = 1; i <= n; ++i) if (!deg[i]) st[i] = 1, q.push(i);
while (q.size()) {
int u = q.front(); q.pop();
for (auto v : e[u]) {
deg[v] --;
if (!deg[v]) st[v] = 1, q.push(v);
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i) {
int u, v; scanf("%d%d", &u, &v);
e[v].push_back(u), deg[u] ++;
}
toposort();
for (int i = 1; i <= n; ++i) if (!st[i]) cnt ++;
printf("%d\n", cnt);
return 0;
}
ABC245G. Foreign Friends:最短路(蓝)
我们枚举颜色的每个二进制位,每次把当前二进制位上为 \(0\) 的颜色加入起始点中,更新所有颜色为当前二进制位上为 \(1\) 的点的答案。再用二进制位上为 \(1\) 的更新为 \(0\) 的。这是因为若两个颜色不同,则它们至少有一个二进制不同。
时间复杂度 \(O(n\log n\log k)\)。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pii;
const int N = 1e5+10;
int n, m, k, l, col[N]; vector<pii> e[N]; vector<int> start;
ll dist[N], ans[N]; bool st[N];
void dijkstra(int bit, int now) {
memset(dist, 0x7f, sizeof dist), memset(st, 0, sizeof st);
priority_queue<pii, vector<pii>, greater<pii>> q;
for (auto u : start) if (!(col[u]>>bit&1^now)) q.push({0, u}), dist[u] = 0;
while (q.size()) {
auto t = q.top(); q.pop();
int u = t.second; ll dis = t.first;
if (st[u]) continue; st[u] = 1;
if (col[u]>>bit&1^now) ans[u] = min(ans[u], dist[u]);
for (auto [v, w] : e[u]) {
if (dist[v] > dis+w)
dist[v] = dis+w, q.push({dist[v], v});
}
}
}
int main() {
int u, v, w;
scanf("%d%d%d%d", &n, &m, &k, &l);
for (int i = 1; i <= n; ++i) scanf("%d", &col[i]);
for (int i = 1; i <= l; ++i) scanf("%d", &u), start.push_back(u);
for (int i = 1; i <= m; ++i) {
scanf("%d%d%d", &u, &v, &w);
e[u].push_back({v, w}), e[v].push_back({u, w});
}
memset(ans, 0x7f, sizeof ans);
for (int i = 0; 1<<(i-1) <= k; ++i) dijkstra(i, 0), dijkstra(i, 1);
for (int i = 1; i <= n; ++i) printf("%lld ", ans[i]>=1e18?-1:ans[i]); puts("");
return 0;
}
10.10
ABC244D. Swap Hats:枚举(红)
猜结论:若 \(a_1,b_1\),\(a_2,b_2\),\(a_3,b_3\) 只有一对相同,则输出 No
;否则输出 Yes
。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int cnt; char s[5], t[5];
int main() {
cin >> s[1] >> s[2] >> s[3] >> t[1] >> t[2] >> t[3];
for (int i = 1; i <= 3; ++i) if (s[i] == t[i]) cnt ++;
if (cnt == 1) puts("No"); else puts("Yes");
return 0;
}
ABC244E. King Bombee:dp(黄)
令 \(f_{i,j,0/1}\) 表示考虑到 \(A\) 中前 \(i\) 个数,\(A_i=j\),\(X\) 出现次数模 \(2=0\) 或 \(1\) 的方案数。状态转移是显然的。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 2010, P = 998244353;
int n, m, k, s, t, x, f[N][N][2];
vector<int> e[N];
int main() {
scanf("%d%d%d%d%d%d", &n, &m, &k, &s, &t, &x);
for (int i = 1; i <= m; ++i) {
int u, v; scanf("%d%d", &u, &v);
e[u].push_back(v), e[v].push_back(u);
}
f[0][s][0] = 1;
for (int i = 1; i <= k; ++i) {
for (int j = 1; j <= n; ++j) {
for (auto u : e[j]) {
if (u == x) (f[i][j][0] += f[i-1][u][1]) %= P, (f[i][j][1] += f[i-1][u][0]) %= P;
else (f[i][j][0] += f[i-1][u][0]) %= P, (f[i][j][1] += f[i-1][u][1]) %= P;
}
}
}
printf("%d\n", f[k][t][0]);
return 0;
}
ABC245F. Shortest Good Path:bfs(绿)
我们令一个状态 \(\{state,u\}\) 表示 \(S=state\),走到 \(S\) 的最后一个点为 \(u\) 的最小步数。从 \(\{0,0\}\) 开始 bfs 即可。最后取 \(\min_{1\le u\le n}\{\{state,u\}\}\) 即可。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
const int N = 20, M = 1<<17;
int n, m, g[N][N]; ll ans;
int f[N][M];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i) {
int u, v; scanf("%d%d", &u, &v);
g[u][v] = g[v][u] = 1;
}
memset(f, 0x3f, sizeof f);
queue<pii> q; q.push({0, 0}), f[0][0] = 0;
while (q.size()) {
auto t = q.front(); q.pop();
int u = t.first, now = t.second;
for (int v = 1; v <= n; ++v) {
if (g[u][v] || !u) {
int to = now ^ (1 << v-1);
if (f[v][to] == 0x3f3f3f3f) f[v][to] = f[u][now]+1, q.push({v, to});
}
}
}
for (int i = 1; i < (1<<n); ++i) {
int now = 0x3f3f3f3f;
for (int j = 1; j <= n; ++j) now = min(now, f[j][i]);
ans += now;
}
printf("%lld\n", ans);
return 0;
}
ABC245G. Construct Good Path:构造(蓝)
直接用 dfs 序建树然后模拟即可。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e5+10;
int n, m, cnt[N], sch[N], s[N]; bool st[N];
vector<int> e[N], ee[N], res;
bool dfs(int u) {
bool now = s[u]; st[u] = 1;
for (auto v : e[u]) {
if (st[v]) continue;
now |= dfs(v), ee[u].push_back(v);
}
return sch[u]=now;
}
void get_path(int u, int fa) {
res.push_back(u), cnt[u] ^= 1;
for (auto v : ee[u]) {
if (v == fa || !sch[v]) continue;
get_path(v, u); res.push_back(u), cnt[u] ^= 1;
}
if (cnt[u] ^ s[u]) {
if (u != 1) res.push_back(fa), res.push_back(u), cnt[fa] ^= 1, cnt[u] ^= 1;
else res.pop_back();
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i) {
int u, v; scanf("%d%d", &u, &v);
e[u].push_back(v), e[v].push_back(u);
}
for (int i = 1; i <= n; ++i) scanf("%1d", &s[i]);
dfs(1), get_path(1, 0);
printf("%d\n", res.size());
for (auto x : res) printf("%d ", x); puts("");
return 0;
}
10.11
ABC243D. Moves on Binary Tree:模拟(黄)
由于结果在 long long
范围内,将原数转化为二进制后用 vector
模拟即可。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
#define int long long
const int N = 1e6+10;
int n, x, res; char s[N];
vector<int> nums;
signed main() {
cin >> n >> x >> s+1;
while (x) {
if (x & 1) nums.push_back(1);
else nums.push_back(0);
x >>= 1;
}
reverse(nums.begin(), nums.end());
for (int i = 1; i <= n; ++i) {
if (s[i] == 'U') nums.pop_back();
else if (s[i] == 'L') nums.push_back(0);
else nums.push_back(1);
}
reverse(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); ++i) res += (1ll<<i) * nums[i];
cout << res << '\n';
return 0;
}
ABC243E. Edge Deletion:Floyd(绿)
Floyd 预处理两点间最短路,过程中计算是否存在一条路径 \(u\rightarrow v\) 满足 \(dist(u,v)\le w(u,v)\),且这条路径不与边 \((u,v)\) 重合。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
#define int long long
const int N = 310;
int n, m, cnt, f[N][N]; bool check[N][N];
struct Edge {
int u, v, w;
};
vector<Edge> edge;
void floyd() {
for (int k = 1; k <= n; ++k) {
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
if (f[i][j] >= f[i][k]+f[k][j] && i != k && j != k)
f[i][j] = f[i][k]+f[k][j], check[i][j] = 1;
}
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
memset(f, 0x3f, sizeof f);
for (int i = 1; i <= n; ++i) f[i][i] = 0;
for (int i = 1; i <= m; ++i) {
int u, v, w; cin >> u >> v >> w;
f[u][v] = f[v][u] = w, edge.push_back({u, v, w});
}
floyd();
for (auto [u, v, w] : edge) {
if (check[u][v])
cnt ++;
}
cout << cnt << '\n';
return 0;
}
ABC243F. Lottery:概率 dp,数学(蓝)
抽 \(k\) 次,每个数抽到 \(c_i\) 次的概率为:
\[\dfrac{k!}{\prod_{i=1}^nc_i!} \prod_{i=1}^np_i^{c_i} \]那么这些数一共能组成 \(\dfrac{k!}{\prod_{i=1}^nc_i!}\) 种排列。
考虑 dp,令 \(f_{i,j,k}\) 表示考虑前 \(i\) 个物品,选出 \(j\) 个,选了 \(k\) 次的概率。考虑不选 \(i\) 和选 \(i\) 两种情况:
\[f_{i,j,k}=f_{i-1,j,k}+\sum_{x=1}^k \dfrac{p_i^x}{x!}f_{i-1,j-1,k-x} \]最终答案为 \(f_{n,m,k}\times k!\)。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 55, P = 998244353;
int n, m, k, sum, inv, p[N], fac[N], infac[N], pow[N][N], f[N][N][N];
int power(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = 1ll * res * a % P;
a = 1ll * a * a % P, b >>= 1;
}
return res;
}
int main() {
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; ++i) scanf("%d", &p[i]), sum += p[i];
inv = power(sum, P-2);
for (int i = 1; i <= n; ++i) {
p[i] = 1ll * p[i] * inv % P;
for (int j = 1; j <= k; ++j) pow[i][j] = power(p[i], j);
}
fac[0] = 1; for (int i = 1; i <= k; ++i) fac[i] = 1ll * fac[i-1] * i % P;
infac[k] = power(fac[k], P-2); for (int i = k-1; i >= 0; --i) infac[i] = 1ll * infac[i+1] * (i+1) % P;
for (int i = 0; i <= n; ++i) f[i][0][0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j)
for (int t = 1; t <= k; ++t) {
f[i][j][t] = f[i-1][j][t];
for (int x = 1; x <= t-j+1; ++x)
(f[i][j][t] += 1ll * f[i-1][j-1][t-x] * pow[i][x] % P * infac[x] % P) %= P;
}
}
printf("%d\n", 1ll*f[n][m][k]*fac[k]%P);
return 0;
}
10.12
P2325 [SCOI2005] 王室联邦:dfs,树分块(蓝)
考虑如下的构造方法:
- dfs 整棵树,处理每个节点时,将其一部分子节点分块,将未被分块的子节点返回到上一层;
- 枚举 \(u\) 的每个子节点 \(v\),递归处理完子树后,将每个子节点返回的未被分块的节点添加至集合 \(S\) 中,若 \(|S|\ge B\) 就将 \(S\) 作为一个新的块并将 \(u\) 作为省会,然后清空 \(S\)。
- 处理完所有子树后,将 \(u\) 也加入 \(S\) 中。显然有 \(|S|\le B\)。
由于 \(|S|\le B\),且一个子树返回的大小最多会增加 \(B-1\) 个节点,那么此时每块的大小 \(\in [B,2B)\)。
最后,\(S\) 中可能仍有 \(\le B\) 个节点。我们将这 \(B\) 个节点并入以根节点为省会的最后一个块中,则这个块的大小 \(\in [B,3B)\)。
时间复杂度 \(O(n)\)。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <stack>
using namespace std;
const int N = 1010;
int n, B, idx, root[N], id[N]; vector<int> e[N];
stack<int> s;
void dfs(int u, int fa) {
int now = s.size();
for (auto v : e[u]) {
if (v == fa) continue; dfs(v, u);
if (s.size()-now >= B) {
root[++ idx] = u;
while (s.size() > now) id[s.top()] = idx, s.pop();
}
}
s.push(u);
}
int main() {
scanf("%d%d", &n, &B);
for (int i = 1; i < n; ++i) {
int u, v; scanf("%d%d", &u, &v);
e[u].push_back(v), e[v].push_back(u);
}
dfs(1, 0);
if (!idx) root[++ idx] = 1;
while (s.size()) id[s.top()] = idx, s.pop();
printf("%d\n", idx);
for (int i = 1; i <= n; ++i) printf("%d ", id[i]); puts("");
for (int i = 1; i <= idx; ++i) printf("%d ", root[i]); puts("");
return 0;
}
ABC243G. Sqrt:dp,数学(蓝)
令 \(f_i\) 表示当前末尾为 \(i\) 的序列数量。显然 \(f_1=1\),状态转移方程为:
\[f_{i^2}=\sum_{j=1}^if_j \]那么:
\[\begin{aligned} f_{i^4}=&\sum_{j=1}^{i^2}f_j\\ =&\sum_{j=1}^i (i^2-j^2+1)f_j\\ =&(i^2+1)\sum_{j=1}^i f_i-\sum_{j=1}^ij^2f_j \end{aligned} \]注意此题开根号时要用 long double
。时间复杂度 \(O(n^{1/4})\)。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 6e4+10;
#define int long long
int T, n, f[N], s1[N], s2[N];
signed main() {
scanf("%lld", &T);
f[1] = 1;
for (int i = 2; i <= 60000; ++i) {
for (int j = 1; j <= i/j; ++j)
f[i] += f[j];
}
for (int i = 1; i <= 60000; ++i) s1[i] = s1[i-1]+f[i], s2[i] = s2[i-1]+f[i]*i*i;
while (T -- ) {
scanf("%lld", &n);
int x = pow((long double)n, 0.5), y = pow((long double)n, 0.25);
printf("%lld\n", (x+1)*s1[y]-s2[y]);
}
return 0;
}
10.15
ABC324A. Same:枚举(红)
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, a[110];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for (int i = 2; i <= n; ++i) if (a[i] != a[i-1]) puts("No"), exit(0);
puts("Yes");
return 0;
}
ABC324B. 3-smooth Numbers:枚举(红)
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
long long n;
int main() {
cin >> n;
while (n % 2 == 0) n /= 2;
while (n % 3 == 0) n /= 3;
if (n > 1) puts("No"); else puts("Yes");
return 0;
}
ABC324C. Error Correction:枚举(橙)
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
const int N = 5e5+10;
int n; string s[N];
vector<int> nums;
bool check(int k) {
int siz = s[0].size(), nsiz = s[k].size();
if (siz == nsiz) {
bool check = 1;
for (int i = 0; i < siz; ++i) {
if (!check && s[0][i] != s[k][i]) return 0;
if (check && s[0][i] != s[k][i]) check = 0;
}
} else if (siz == nsiz+1) {
bool check = 1;
for (int i = 0, j = 0; i < siz; ++i, ++j) {
if (!check && s[0][i] != s[k][j]) return 0;
if (check && s[0][i] != s[k][j]) check = 0, ++i;
if (s[0][i] != s[k][j]) return 0;
}
} else if (siz == nsiz-1) {
bool check = 1;
for (int i = 0, j = 0; i < siz; ++i, ++j) {
if (!check && s[0][i] != s[k][j]) return 0;
if (check && s[0][i] != s[k][j]) check = 0, ++j;
if (s[0][i] != s[k][j]) return 0;
}
} else return 0;
return 1;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> s[0];
for (int i = 1; i <= n; ++i) cin >> s[i];
for (int i = 1; i <= n; ++i) if (check(i)) nums.push_back(i);
printf("%d\n", nums.size()); for (auto x : nums) printf("%d ", x); puts("");
return 0;
}
ABC324D. Square Permutation:枚举(黄)
直接枚举排列会超时。发现 \(\sqrt{10^{13}}<4\times 10^6\),于是枚举底数 \(\in [0,4\times 10^6]\) 即可。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;
#define int long long
int n, ans; char s[15]; vector<int> A, B;
bool cmp() {
for (int i = 0; i < A.size(); ++i) if (A[i] != B[i]) return 0;
return 1;
}
signed main() {
cin >> n >> s+1;
for (int i = 1; i <= n; ++i) A.push_back(s[i]-'0'); sort(A.begin(), A.end());
for (int i = 0; i <= 4000000; ++i) {
int x = i*i;
B.clear();
for (int j = 1; j < n; ++j) B.push_back((int)(x/pow(10, j-1))%10);
B.push_back(x/pow(10, n-1)); sort(B.begin(), B.end());
if (cmp()) ans ++;
}
printf("%lld\n", ans);
return 0;
}
ABC324E. Joint Two Strings:双指针,桶,枚举(黄)
双指针处理出 \(S_i\) 和 \(T\) 的最长公共前后缀长度 \(pre_i\) 和 \(suf_i\)。对于 \(S_i\),其答案即为满足 \(suf_j\ge\text{len}(T)-pre_i\) 的 \(S_j\) 数量。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 5e5+10;
int n, pre[N], suf[N], num[N]; ll ans;
string s[N];
int solve(string &A, string &B) {
int i = 0, j = -1;
while (i < A.size() && j+1 < B.size()) {
if (A[i] == B[j+1]) j ++;
i ++;
}
return j+1;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> s[0];
for (int i = 1; i <= n; ++i) cin >> s[i];
for (int i = 1; i <= n; ++i) pre[i] = solve(s[i], s[0]), reverse(s[i].begin(), s[i].end());
reverse(s[0].begin(), s[0].end());
for (int i = 1; i <= n; ++i) suf[i] = solve(s[i], s[0]), num[suf[i]] ++;
for (int i = s[0].size()-1; ~i; --i) num[i] += num[i+1];
for (int i = 1; i <= n; ++i) ans += num[s[0].size()-pre[i]];
printf("%lld\n", ans);
return 0;
}
ABC324F. Beautiful Path:01分数规划,二分,bfs(拓扑排序),dp(绿)
假设我们依次经过边 \(e_1,e_2,\cdots,e_k\),则我们要求的即为:
\[\dfrac{\sum_{i=1}^{k} b_{e_i} }{\sum_{i=1}^k c_{e_i}} \]的最大值。这是一个经典的 01 规划问题,可以通过二分解决。
假设 \(\dfrac{\sum_{i=1}^{k} b_{e_i} }{\sum_{i=1}^k c_{e_i}} \ge X\),那么有 \(\sum_{i=1}^{k} (b_{e_i}-Xc_{e_i})\ge 0\)。我们令 \(w_i=b_i-Xc_{e_i}\),在图上跑一遍 dp,计算出 \(1\) 号点到 \(n\) 号点的最大权值之和,若 \(f_n\ge 0\) 则说明答案 \(\ge X\)。
时间复杂度 \(O((n+m)\log W)\)。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int, double> pid;
const int N = 2e5+10;
const double eps = 1e-12, INF = 1e9;
struct Edge {
int u, v, b, c;
};
int n, m, deg[N]; double f[N];
vector<pid> e[N]; vector<Edge> Edg;
void bfs() {
for (int i = 2; i <= n; ++i) f[i] = -INF;
queue<int> q;
for (int i = 1; i <= n; ++i) if (!deg[i]) q.push(i);
while (q.size()) {
int u = q.front(); q.pop();
for (auto [v, w] : e[u]) {
deg[v] --, f[v] = max(f[v], f[u]+w);
if (!deg[v]) q.push(v);
}
}
}
bool check(double x) {
for (int i = 1; i <= n; ++i) e[i].clear();
for (auto [u, v, b, c] : Edg) {
double w = b - x*c;
e[u].push_back({v, w}), deg[v] ++;
}
bfs();
return (f[n] >= 0);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i) {
int u, v, b, c; scanf("%d%d%d%d", &u, &v, &b, &c);
Edg.push_back({u, v, b, c});
}
double l = 0, r = 2e9;
while (l+eps < r) {
double mid = (l + r) / 2;
if (check(mid)) l = mid;
else r = mid;
}
printf("%.12f\n", l);
return 0;
}
10.16
P2553 [AHOI2001] 多项式乘法:模拟(蓝)
难点在读入字符串。时间复杂度 \(O(n^2)\)。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 80;
int a[N], b[N], c[N];
string s;
int main() {
while (getline(cin, s)) {
memset(a, 0, sizeof a), memset(b, 0, sizeof b), memset(c, 0, sizeof c);
bool ismul = 0;
for (auto c : s) if (c == '*') ismul = 1;
if (!ismul) {
for (auto c : s) if (isdigit(c) || c == '+' || c == '*') cout << c;
cout << '\n';
continue;
} else {
bool ispow = 0; int i, num = 0, power = 0;
for (i = 0; i < s.size(); ++i) {
char c = s[i];
if (!isdigit(c)) {
if (c == '(' || c == '*') continue;
else if (c == '^') ispow = 1;
else if (c == '+' || c == ')') {
a[power] += num, num = 0, power = 0, ispow = 0;
if (c == ')') break;
}
} else {
if (!ispow) num = num * 10 + c - '0';
else power = power * 10 + c - '0';
}
}
for (i = i+1 ; i < s.size(); ++i) {
char c = s[i];
if (!isdigit(c)) {
if (c == '(' || c == '*') continue;
else if (c == '^') ispow = 1;
else if (c == '+' || c == ')') {
b[power] += num, num = 0, power = 0, ispow = 0;
if (c == ')') break;
}
} else {
if (!ispow) num = num * 10 + c - '0';
else power = power * 10 + c - '0';
}
}
for (int i = 0; i <= 30; ++i) {
for (int j = 0; j <= 30; ++j)
c[i+j] += a[i]*b[j];
}
bool check = 0;
for (int i = 60; i >= 0; --i) {
if (c[i]) {
if (check) printf("+");
printf("%d", c[i]);
if (i) printf("a^%d", i);
check = 1;
}
}
puts("");
}
}
return 0;
}
ABC242D. ABC Transform:模拟,递归,思维(黄)
令 \(f(t,k)\) 表示 \(S^{(t)}\) 的第 \(k\) 个字母。显然 \(f(0,k)=S^{(0)}_k\),\(f(t,0)=(S^{(0)}_0+t)\bmod 3\)。递归的过程是显然的:
\[f(t,k)=\left\{\begin{aligned} &(f(t-1,k/2)+2)\bmod 3 &(k\bmod 2=0)\\ &(f(t-1,(k+1)/2)+1)\bmod 3 &(k\bmod 2=1) \end{aligned}\right. \](\(A,B,C\) 分别为 \(0,1,2\))
时间复杂度 \(O(q\log k)\)。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 1e5+10;
int q; char s[N];
char f(ll t, ll k) {
if (!t) return s[k];
if (k == 1) return 'A' + (s[1]-'A'+t) % 3;
return 'A' + (f(t-1, (k+1)/2)-'A'+(k&1^1)+1) % 3;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> s+1 >> q;
ll t, k;
while (q -- ) {
cin >> t >> k;
cout << f(t, k) << '\n';
}
return 0;
}
ABC242E. (∀x∀):组合数学(绿)
由于是回文串,我们只考虑前 \(\left\lfloor \dfrac{n+1}{2} \right\rfloor\) 个字符即可。对于 \(X_i\),若 \(X_i<S_i\),则后面 \(\left\lfloor \dfrac{n+1}{2} \right\rfloor-i\) 个字符可以随便选,共有 \((S_i-\texttt{A})\times 26^{\left\lfloor (n+1)/2 \right\rfloor-i}\) 种选择(\(\forall 1\le j<i,X_j=S_j\))。
最后还需要考虑 \(X\) 的前半部分和 \(S\) 的前半部分相等的情况。注意预处理 \(26\) 的幂。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e6+10, P = 998244353;
int T, n, power[N]; char s[N];
void solve() {
int ans = 0; bool check = 1;
cin >> n >> s+1;
for (int i = 1; i <= (n+1)/2; ++i) (ans += 1ll * (s[i]-'A') * power[(n+1)/2-i] % P) %= P;
for (int i = n/2; i >= 1; --i) {
if (s[i] < s[n-i+1]) {check = 1; break;}
else if (s[i] > s[n-i+1]) {check = 0; break;}
}
cout << (ans+check)%P << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
power[0] = 1;
for (int i = 1; i <= 1000000; ++i) power[i] = 26ll * power[i-1] % P;
cin >> T;
while (T -- ) solve();
return 0;
}
10.20
P1229 遍历问题:树,数学(黄)
注意到对于一个节点 \(u\),若其有且仅有一个子节点 \(v\),则 \(v\) 可以作为 \(u\) 的左儿子或右儿子。假设一共有 \(cnt\) 个只有一个儿子的节点,则答案为 \(2^{cnt}\)。
根据前后序遍历的性质,若 \(pre_i=suf_j\) 且 \(pre_{i+1}=suf_{j-1}\),则 \(pre_i\) 只有一个儿子 \(pre_{i+1}\)。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 5010;
int n, cnt;
char pre[N], suf[N];
int main() {
cin >> pre+1 >> suf+1; n = strlen(pre+1);
for (int i = 1; i <= n; ++i) {
for (int j = 2; j <= n; ++j)
if (pre[i] == suf[j] && pre[i+1] == suf[j-1])
cnt ++;
}
cout << pow(2, cnt) << '\n';
return 0;
}
P5186 [COCI2009-2010#4] OGRADA:单调队列(绿)
(双倍经验:P7697 [COCI2009-2010#4] OGRADA)
对于第一问,首先求出每一次刷最大能刷的值,这个可以用单调队列求;然后对于每个栅栏,求出每个覆盖它的部分的最大值,也可以用单调队列求。用总和减去每个栅栏能刷到的最大值之和即可。
对于第二问,假设区间 \([l,r]\) 的栅栏都被粉刷为了相同的高度,那么这一段区间对答案的贡献即为 \(\left\lceil\dfrac{r-l+1}{x}\right\rceil\)。
时间复杂度 \(O(n)\)。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <deque>
using namespace std;
const int N = 1e6+10;
int n, x, cnt; long long sum, ans;
int h[N], p[N], col[N];
int main() {
scanf("%d%d", &n, &x);
for (int i = 1; i <= n; ++i) scanf("%d", &h[i]), sum += h[i];
// 预处理每一段区间的最小值
deque<int> q;
for (int i = 1; i < x; ++i) {
while (q.size() && h[q.back()] > h[i]) q.pop_back();
q.push_back(i);
}
for (int i = x; i <= n; ++i) {
while (q.size() && q.front() < i-x+1) q.pop_front();
while (q.size() && h[q.back()] > h[i]) q.pop_back();
q.push_back(i), p[i-x+1] = h[q.front()];
}
// 找到每一次上色的最大值
q.clear();
for (int i = 1; i <= n; ++i) {
while (q.size() && q.front() < i-x+1) q.pop_front();
while (q.size() && i <= n-x+1 && p[q.back()] < p[i]) q.pop_back();
if (i <= n-x+1) q.push_back(i);
ans += col[i] = p[q.front()];
}
printf("%lld\n", sum-ans);
// 找到连续段
int siz = 0;
for (int i = 1; i <= n; ++i) {
if (col[i] != col[i-1]) cnt += ceil((double)siz/x), siz = 1;
else siz ++;
}
cnt += ceil((double)siz/x);
printf("%d\n", cnt);
return 0;
}
P1841 [JSOI2007] 重要的城市:Floyd,dp(蓝)
在计算最短路的同时,计算最短路的条数。对于点 \(i\),若存在点 \(j,k\) 满足 \(g(j,k)=g(j,i)+g(i,k)\) 且 \(num(j,k)=num(j,i)\times num(i,k)\),则 \(i\) 为 \(j\rightarrow k\) 路径上的必经点。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 210;
int n, m, cnt, g[N][N], num[N][N];
vector<int> e[N];
void floyd() {
for (int k = 1; k <= n; ++k) {
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
if (g[i][j] > g[i][k]+g[k][j]) g[i][j] = g[i][k]+g[k][j], num[i][j] = num[i][k]*num[k][j];
else if (g[i][j] == g[i][k]+g[k][j]) num[i][j] += num[i][k]*num[k][j];
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j)
g[i][j] = (i == j) ? 0 : 1e9;
}
for (int i = 1; i <= m; ++i) {
int u, v, w; scanf("%d%d%d", &u, &v, &w);
g[u][v] = g[v][u] = min(g[u][v], w), num[u][v] = num[v][u] = 1, e[u].push_back(v);
}
floyd();
for (int i = 1; i <= n; ++i) {
bool check = 0;
for (int j = 1; j <= n; ++j) {
for (int k = 1; k <= n; ++k) {
if (i == j || i == k || j == k) continue;
if (g[j][k] == g[j][i]+g[i][k] && num[j][k] == num[j][i]*num[i][k])
check = 1;
}
}
if (check) printf("%d ", i), cnt ++;
}
if (!cnt) puts("No important cities.");
return 0;
}
CF1100E. Andrew and Taxi:二分,拓扑排序(紫)
答案显然具有单调性,即若 \(x\) 符合题意,\(x+1\) 必定也符合题意。所以我们可以二分解决问题。
如何判断 \(x\) 是否合法呢?我们可以把所有边 \((u,v,w)\) 分为两类:
- \(w>x\):这一部分边是不能调整方向的,可以通过拓扑排序判断是否有环。若有环则 \(x\) 一定不是合法解。
- \(w\le x\):这一部分边是可以调整方向的。若 \(u\) 的拓扑序大于 \(v\) 的拓扑序,则会产生环,需要把这条边反过来。
时间复杂度 \(O((n+m)\log w)\)。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
const int N = 1e5+10;
struct Edge {
int u, v, w;
} edge[N];
int n, m, idx, deg[N], topo[N];
vector<int> e[N], ans;
bool toposort() {
idx = 0; queue<int> q; int cnt = 0;
for (int i = 1; i <= n; ++i) if (!deg[i]) q.push(i);
while (q.size()) {
int u = q.front(); q.pop();
topo[u] = ++ idx, cnt ++;
for (auto v : e[u]) {
deg[v] --;
if (!deg[v]) q.push(v);
}
}
return (cnt == n);
}
bool check(int x) {
for (int i = 1; i <= n; ++i) e[i].clear(), deg[i] = 0;
for (int i = 1; i <= m; ++i) {
int u = edge[i].u, v = edge[i].v, w = edge[i].w;
if (w > x) e[u].push_back(v), deg[v] ++;
}
if (!toposort()) return 0;
ans.clear();
for (int i = 1; i <= m; ++i) {
int u = edge[i].u, v = edge[i].v, w = edge[i].w;
if (w <= x && topo[u] > topo[v]) ans.push_back(i);
}
return 1;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i) {
int u, v, w; scanf("%d%d%d", &u, &v, &w);
edge[i] = {u, v, w};
}
int l = 0, r = 1e9+1;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid+1;
}
printf("%d %d\n", l, ans.size());
for (auto u : ans) printf("%d ", u); puts("");
return 0;
}
P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G:有向图强连通分量(tarjan)(绿)
缩点后,若只存在一个出度为 \(0\) 的 SCC,则答案即为这个 SCC 内点的数量。否则答案为 \(0\)。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e4+10;
int n, m; vector<int> e[N];
int idx, top, cnt, dfn[N], low[N], stk[N], ins[N], c[N], siz[N], deg[N];
void tarjan(int u) {
dfn[u] = low[u] = ++ idx, stk[++ top] = u, ins[u] = 1;
for (auto v : e[u]) {
if (!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]);
else low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
cnt ++; int t;
do t = stk[top --], ins[t] = 0, c[t] = cnt, siz[cnt] ++;
while (u != t);
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i) {
int u, v; scanf("%d%d", &u, &v);
e[u].push_back(v);
}
for (int i = 1; i <= n; ++i) if (!dfn[i]) tarjan(i);
for (int i = 1; i <= n; ++i) {
for (auto v : e[i]) {
if (c[i] != c[v])
deg[c[i]] ++;
}
}
int now = 0;
for (int i = 1; i <= cnt; ++i) {
if (!deg[i]) {
if (now) puts("0"), exit(0);
else now = i;
}
}
printf("%d\n", siz[now]);
return 0;
}