题目链接
https://codeforces.com/gym/101879
参考题解
B - Aesthetics in poetry
简要题意:
给一个长度为 n n n 的数组 a i a_i ai,再给一个数 k k k,求最小的 j j j 满足: j j j 大于 1 1 1 且整除 n n n,并且 a i a_i ai 除以 k k k 的余数恰有 k k k 种,每种有 n k \frac{n}{k} kn 个。无解输出 − 1 -1 −1。
解题思路:
范围不大,暴力判断。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int a[maxn], cnt[maxn];
int n;
int check(int k){
if(n % k) return 0;
for(int i = 1; i <= n; ++i){
++cnt[a[i] % k];
}
int ret = 1;
for(int i = 0; i < k; ++i){
if(cnt[i] != n / k) { ret = 0; break; }
}
for(int i = 0; i < k; ++i) cnt[i] = 0;
return ret;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i];
int ret = -1;
for(int i = 2; i <= n; ++i){
if(check(i)) { ret = i; break; }
}
cout << ret << endl;
return 0;
}
C - Promenade by the lake
简要题意:
给一个 n n n 个顶点、 m m m 条边的无向图,再给 k k k 条侯选边,问能否添加若干条侯选边使得原图有欧拉回路。
解题思路:
无向图有欧拉回路当且仅当所有点度数为偶数,侯选边组成的图中,若有环,则去掉环对答案无影响,故侯选边构成一棵树,接着就变成一个树形 d p dp dp 问题:每条边可选或不选、要让所有点度数为偶数,可以自底向上逐一确定答案。(这里犯傻写了树形 d p dp dp,输出答案部分需要好好考虑一下)
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 6e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
vector<pii> G[maxn], P[maxn][2], ans;
vector<int> rt;
pii ei[maxn];
int deg[maxn], dp[maxn][2], pre[maxn], vis[maxn];
int n, m, k;
int fin(int x){
return x == pre[x] ? x : pre[x] = fin(pre[x]);
}
int dfs(int u, int f){
dp[u][deg[u]] = 1, vis[u] = 1;
int pu = u, p = u;
for(auto &e : G[u]){
int v = e.first;
if(v == f) continue;
int pv = dfs(v, u); p = e.second;
// dp[p][0] |= dp[pu][0] & dp[pv][0];
// dp[p][0] |= dp[pu][1] & dp[pv][1]; // xuan
// dp[p][1] |= dp[pu][0] & dp[pv][1]; // xuan
// dp[p][1] |= dp[pu][1] & dp[pv][0];
for(int i = 0; i < 2; ++i){
for(int j = 0; j < 2; ++j){
if(dp[pu][i] && dp[pv][j]){
dp[p][i ^ j] = 1;
P[p][i ^ j].pb(pii{pu, i});
P[p][i ^ j].pb(pii{pv, j});
}
}
}
pu = p;
}
return p;
}
void dfs2(int x, int y){
if(x <= n) return;
auto e1 = P[x][y][0], e2 = P[x][y][1];
if(y != e1.second) ans.pb(ei[x - n]);
dfs2(e1.first, e1.second);
dfs2(e2.first, e2.second);
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m >> k;
for(int i = 1; i <= m; ++i){
int u, v; cin >> u >> v;
deg[u] ^= 1, deg[v] ^= 1;
}
for(int i = 1; i <= n; ++i){
pre[i] = i;
}
for(int i = 1; i <= k; ++i){
int u, v; cin >> u >> v;
if(fin(u) == fin(v)) continue;
pre[fin(u)] = fin(v);
G[u].pb(pii{v, n + i}), G[v].pb(pii{u, n + i});
ei[i] = pii{u, v};
// cout << u << " -> " << v << endl;
}
// for(int i = 1; i <= n; ++i){
// cout << i << " ?? " << deg[i] << endl;
// }
int ret = 1;
for(int i = 1; i <= n; ++i){
if(vis[i]) continue;
int u = dfs(i, 0);
ret &= dp[u][0];
rt.pb(u);
}
// for(int i = 1; i <= n + k; ++i){
// cout << i << " ?? " << dp[i][0] << " " << dp[i][1] << endl;
// }
if(!ret){
cout << "NO" << endl;
return 0;
}
cout << "YES" << "\n";
for(auto &v : rt){
dfs2(v, 0);
}
cout << sz(ans) << "\n";
for(auto &e : ans){
cout << e.first << " " << e.second << "\n";
}
return 0;
}
D - Maximizing Advertising
简要题意:
二维平面给 n n n 个点,点为黑色或白色,现要用两个不相交的矩形分别圈住某些点,要求让某个矩形的白点与另一个矩形的黑点个数和最大,求最大值。
解题思路:
可通过反证得知两个矩形圈住所有点,并且边界在点坐标处,并且要么一左一右、要么一上一下,分别枚举 x , y x,y x,y 分割处即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
struct Node{
int x, y, c;
} a[maxn];
int n;
int cmpX(const Node &a, const Node &b){
return a.x < b.x;
}
int cmpY(const Node &a, const Node &b){
return a.y < b.y;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
int tot[2] = {};
for(int i = 1; i <= n; ++i){
int x, y; char ch[2]; cin >> x >> y >> ch;
a[i] = Node{x, y, ch[0] == 'b'};
tot[ch[0] == 'b']++;
}
int ret = 0;
sort(a + 1, a + 1 + n, cmpX);
int cnt[2] = {};
for(int l = 1, r; l <= n; l = r){
r = l;
while(r <= n && a[r].x == a[l].x){
++cnt[a[r].c];
++r;
}
ret = max(ret, cnt[0] + (tot[1] - cnt[1]));
ret = max(ret, cnt[1] + (tot[0] - cnt[0]));
}
sort(a + 1, a + 1 + n, cmpY);
cnt[0] = cnt[1] = 0;
for(int l = 1, r; l <= n; l = r){
r = l;
while(r <= n && a[r].y == a[l].y){
++cnt[a[r].c];
++r;
}
ret = max(ret, cnt[0] + (tot[1] - cnt[1]));
ret = max(ret, cnt[1] + (tot[0] - cnt[0]));
}
cout << ret << endl;
return 0;
}
E - Group work
简要题意:
签到题。
解题思路:
签到题。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 6e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int n; cin >> n;
cout << (1 << n) - n - 1 << endl;
return 0;
}
F - Optimizing Transportation in Portugal
简要题意:
给定一个 n n n 个顶点、 m m m 条带权边的无向图,再给起点 s s s 和终点 t t t,问删去一条边,最多能让 s s s 到 t t t 的最短路变成多长。
解题思路:
从 s s s到 t t t 随便扣一条最短路 P P P,只有删除 P P P 上的边才对答案有影响,枚举删边跑最短路显然不可取,故反向考虑其他边对 P P P 删边后最短路的贡献。枚举边 ( u , v , w ) (u, v, w) (u,v,w),考虑 s → u → v → t s \rightarrow u \rightarrow v \rightarrow t s→u→v→t,要在这条路径最短的情况下,使得经过 P P P 的前缀、后缀部分尽可能短,即需要找到 u u u 到 P P P 上距离 s s s最近的点 x x x、 v v v 到 P P P 上距离 t t t 最近的点 y y y,如果删去 x x x 到 y y y 的边,就仍存在一条 s → u → v → t s \rightarrow u \rightarrow v \rightarrow t s→u→v→t 的最短路径。 x x x 和 y y y 可以通过一次拓扑转移得到,贡献表现为区间内有贡献,扫描线处理即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 5e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll oo = 1ll << 60;
typedef pair<ll, int> pli;
struct Edge{
int u, v, w, p;
};
vector<ll> val[maxn];
vector<Edge> G[maxn], G1[maxn], G2[maxn];
ll dis1[maxn], dis2[maxn];
int in[maxn], dep1[maxn], dep2[maxn], low1[maxn], low2[maxn], hasE[maxn], preV[maxn], preE[maxn];
int n, m, S, T, tot;
void dij(int s, ll dis[]){
priority_queue<pli, vector<pli>, greater<pli> > q;
for(int i = 1; i <= n; ++i) dis[i] = oo;
dis[s] = preV[s] = preE[s] = 0, q.push({dis[s], s});
while(!q.empty()){
int u = q.top().second; ll d = q.top().first; q.pop();
for(auto &e : G[u]){
if(dis[e.v] > dis[u] + e.w){
dis[e.v] = dis[u] + e.w;
preV[e.v] = u, preE[e.v] = e.p;
q.push({dis[e.v], e.v});
}
}
}
}
void build(vector<Edge> g[], ll dis[], int dep[], int low[]){
for(int i = 1; i <= n; ++i){
in[i] = 0;
low[i] = dep[i] ? dep[i] : n + 1;
}
for(int i = 1; i <= n; ++i){
for(auto &e : G[i]){
if(dis[e.v] == dis[i] + e.w) g[i].pb(Edge{i, e.v, e.w, e.p}), ++in[e.v];
}
}
queue<int> q;
for(int i = 1; i <= n; ++i){
if(in[i] == 0) q.push(i);
}
while(!q.empty()){
int u = q.front(); q.pop();
for(auto &e : g[u]){
if(!dep[e.v]) low[e.v] = min(low[e.v], low[u]);
if(--in[e.v] == 0) q.push(e.v);
}
}
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m >> S >> T;
for(int i = 1; i <= m; ++i){
int u, v, w; cin >> u >> v >> w;
G[u].pb(Edge{u, v, w, i});
G[v].pb(Edge{v, u, w, i});
}
dij(S, dis1);
dij(T, dis2);
// cout << dis1[T] << endl;
for(int u = S; u; u = preV[u]){
dep1[u] = ++tot, hasE[preE[u]] = tot;
// cout << u << " ";
}
// cout << endl;
for(int u = S; u; u = preV[u]){
dep2[u] = tot - dep1[u] + 1;
}
build(G1, dis1, dep1, low1);
build(G2, dis2, dep2, low2);
for(int i = 1; i <= n; ++i){
low2[i] = tot - low2[i] + 1;
// cout << i << " ?? " << low1[i] << " " << low2[i] << endl;
}
for(int i = 1; i <= n; ++i){
for(auto &e : G[i]){
if(low1[e.u] >= low2[e.v] || hasE[e.p]) continue;
ll d = dis1[e.u] + e.w + dis2[e.v];
val[low1[e.u]].pb(d);
val[low2[e.v]].pb(-d);
// cout << low1[e.u] << " -> " << low2[e.v] << " " << d << endl;
}
}
ll ret = dis1[T];
multiset<ll> st;
for(int i = 1; i < tot; ++i){
sort(val[i].begin(), val[i].end());
for(auto &v : val[i]){
if(v < 0) st.erase(st.find(-v));
else st.emplace(v);
}
ll tmp = st.empty() ? oo : *st.begin();
// if(tmp == oo) cout << i << " ?? " << endl;
ret = max(ret, tmp);
}
cout << (ret < 2e9 ? ret : -1) << endl;
return 0;
}
G - Running a penitentiary
简要题意:
给定 n n n 个区间, q q q 次操作,每次要么修改一个区间,要么询问 [ l , r ] [l, r] [l,r] 编号的区间交长度。
解题思路:
交区间为 [ max { l i } , min { r i } ] [\max \{l_i\}, \min\{r_i\}] [max{li},min{ri}],分别维护区间左右端点的最值即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int li[maxn], ri[maxn];
int n, q;
struct SegTree{
int mx[maxn << 2];
void pushUp(int rt){
mx[rt] = max(mx[lson], mx[rson]);
}
void build(int l, int r, int rt, int a[]){
if(l == r){
mx[rt] = a[l];
return;
}
int mid = gmid;
build(l, mid, lson, a);
build(mid + 1, r, rson, a);
pushUp(rt);
}
void update(int l, int r, int rt, int pos, int val){
if(l == r){
mx[rt] = val;
return;
}
int mid = gmid;
if(pos <= mid) update(l, mid, lson, pos, val);
else update(mid + 1, r, rson, pos, val);
pushUp(rt);
}
int query(int l, int r, int rt, int L, int R){
if(l >= L && r <= R) return mx[rt];
int mid = gmid, ret = -inf;
if(L <= mid) ret = max(ret, query(l, mid, lson, L, R));
if(R > mid) ret = max(ret, query(mid + 1, r, rson, L, R));
return ret;
}
} tr_l, tr_r;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> q;
for(int i = 1; i <= n; ++i){
cin >> li[i] >> ri[i];
ri[i] = -ri[i];
}
tr_l.build(1, n, 1, li);
tr_r.build(1, n, 1, ri);
while(q--){
char ch[2]; int x, y, z; cin >> ch >> x >> y;
if(ch[0] == 'C'){
cin >> z;
tr_l.update(1, n, 1, x, y);
tr_r.update(1, n, 1, x, -z);
}
else{
int l = tr_l.query(1, n, 1, x, y);
int r = -tr_r.query(1, n, 1, x, y);
int ret = l > r ? 0 : r - l + 1;
cout << ret << "\n";
}
}
return 0;
}
H - Wine Production
简要题意:
给定长度为 n n n 的数组 a i a_i ai, q q q 次询问,每次询问区间 [ l , r ] [l, r] [l,r] 最大的 k k k,满足区间至少有 k k k 个不同的数,每个重复 k k k 次及以上。
解题思路:
莫队可做,答案 k k k 不超过 n \sqrt{n} n,区间转移时维护每种频率的数的个数,统计时枚举答案即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 3e4 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int a[maxn], xi[maxn], cntX;
int id[maxn], cnt[maxn], sum[maxn], ans[maxn];
int n, m;
struct Qr{
int l, r, p;
bool operator < (const Qr &o) const{
return id[l] == id[o.l] ? (id[l] & 1 ? r > o.r : r < o.r) : id[l] < id[o.l];
}
} qr[maxn];
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; ++i){
cin >> a[i];
xi[++cntX] = a[i];
}
for(int i = 1; i <= m; ++i){
cin >> qr[i].l >> qr[i].r;
qr[i].p = i;
}
sort(xi + 1, xi + 1 + cntX);
cntX = unique(xi + 1, xi + 1 + cntX) - xi - 1;
for(int i = 1; i <= n; ++i){
a[i] = lower_bound(xi + 1, xi + 1 + cntX, a[i]) - xi;
}
int len = sqrt(n);
for(int i = 1; i <= n; ++i){
id[i] = (i - 1) / len + 1;
}
sort(qr + 1, qr + 1 + m);
int l = 1, r = 0, cntH = 0;
auto add = [&](int x){
--sum[cnt[x]];
++cnt[x];
++sum[cnt[x]];
if(cnt[x] == len + 1) ++cntH;
};
auto del = [&](int x){
--sum[cnt[x]];
--cnt[x];
++sum[cnt[x]];
if(cnt[x] == len) --cntH;
};
for(int i = 1; i <= m; ++i){
while(l > qr[i].l) --l, add(a[l]);
while(r < qr[i].r) ++r, add(a[r]);
while(l < qr[i].l) del(a[l]), ++l;
while(r > qr[i].r) del(a[r]), --r;
int cur = cntH;
for(int j = len; j >= 1; --j){
cur += sum[j];
if(cur >= j) { ans[qr[i].p] = j; break; }
}
}
for(int i = 1; i <= m; ++i){
cout << ans[i] << "\n";
}
return 0;
}
I - A story about tea
简要题意:
给一个 n n n 个盘子的汉诺塔,问能否恰在 k k k 次移动后完成。
解题思路:
若 k < 2 n − 1 k \lt 2^n - 1 k<2n−1,则无解。否则考虑 x = k − ( 2 n − 1 ) x = k - (2^n - 1) x=k−(2n−1),若 x x x 为偶数,让第一个盘子反复横跳即可;若 x x x 为奇数,反复横跳后剩一次,在最后一步还原时额外移动一次。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
typedef pair<char, char> pcc;
vector<pcc> ans;
int n, k;
void dfs(char A, char B, char C, int n){
if(!n) return;
dfs(A, C, B, n - 1);
ans.pb(pcc{A, C});
dfs(B, A, C, n - 1);
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> k;
if(k < (1 << n) - 1){
cout << "N" << endl;
return 0;
}
cout << "Y" << "\n";
int res = k - ((1 << n) - 1);
while(res > 1){
cout << "A B" << "\n";
cout << "B A" << "\n";
res -= 2;
}
dfs('A', 'B', 'C', n);
for(int i = 0; i < sz(ans) - 1; ++i){
cout << ans[i].first << " " << ans[i].second << "\n";
}
auto e = ans.back();
if(res){
if(e.first == 'A'){
cout << "A B" << "\n";
cout << "B C" << "\n";
}
else{
cout << "B A" << "\n";
cout << "A C" << "\n";
}
}
else{
cout << e.first << " " << e.second << "\n";
}
return 0;
}
J - Meme Wars
简要题意:
签到题。
解题思路:
暴力拼接。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int n; cin >> n;
string s = "";
for(char i = 'a'; i <= 'z' && sz(s) < n; ++i){
s = s + i + s;
}
cout << s[n - 1] << endl;
return 0;
}