题目链接
https://codeforces.com/gym/102423
参考题解
A - Carryless Square Root
简要题意:
本题定义加法为不进位加法,如 3 + 8 = 1 3 + 8 = 1 3+8=1,乘法按竖式乘法计算,不进位。给定 n ( 1 ≤ n ≤ 1 0 25 ) n(1 \leq n \leq 10^{25}) n(1≤n≤1025),求满足 a ∗ a = n a*a=n a∗a=n 的最小的 a a a,无解输出 − 1 -1 −1。
解题思路:
若 n n n 为偶数,无解。否则 a a a 的位数为 n + 1 2 \frac{n + 1}{2} 2n+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;
vector<pii> vc[30];
int a[30], b[30], sum[30];
int n, m;
int check(int l, int r){
for(int i = l; i <= r; ++i){
int tmp = 0;
for(auto &e : vc[i]){
(tmp += b[e.first] * b[e.second]) %= 10;
}
if(tmp != a[i]) return 0;
}
return 1;
}
int dfs(int pn, int pm){
if(pm < 0) return check(0, m - 2);
int l = pm == m - 1 ? 1 : 0;
for(int i = l; i < 10; ++i){
b[pm] = i;
if(check(pn, pn) && dfs(pn - 1, pm - 1)) return 1;
}
return 0;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
string s; cin >> s;
reverse(s.begin(), s.end());
n = s.size();
for(int i = 0; i < n; ++i) a[i] = s[i] - '0';
if(n % 2 == 0){
cout << "-1\n";
return 0;
}
m = (n + 1) / 2;
for(int i = 0; i < m; ++i){
for(int j = 0; j < m; ++j) vc[i + j].pb(pii{i, j});
}
if(dfs(n - 1, m - 1)){
for(int i = m - 1; i >= 0; --i) cout << b[i]; cout << endl;
}
else{
cout << "-1\n";
}
return 0;
}
B - Computer Cache
简要题意:
给定一块 n n n 个字节的缓存,再有 m m m 个数据块,第 i i i 个有 k i k_i ki 字节。接下来 q q q 个操作:① 1 , i , p 1, i, p 1,i,p,表示将第 i i i 个数据块存入缓存的 [ p , p + k i − 1 ] [p, p + k_i - 1] [p,p+ki−1] 位置;② 2 , p 2, p 2,p,询问当前缓存第 p p p 个字节的值;③ 3 , i , l , r 3, i, l, r 3,i,l,r,将第 i i i 个数据块 [ l , r ] [l, r] [l,r] 字节的值加一。
解题思路:
用线段树维护缓存每个字节的数据来源,每次 ① 操作即为区间赋值,记录来源的数据块编号、当前存入的起始位置、当前数据块版本。对于 ③ 操作,用主席树维护每个数据块每个版本的值。② 操作单点询问,先获取到原始值,再对应查询主席树中的增量,两者相加为答案。
参考代码:
#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 = 11092019;
const int inf = 0x3f3f3f3f;
int a[maxn], li[maxn], len[maxn], bel[maxn];
int n, m, q, tot;
struct SegTree{
int cov[maxn << 2];
void pushDown(int rt){
if(cov[rt]){
cov[lson] = cov[rson] = cov[rt];
cov[rt] = 0;
}
}
void update(int l, int r, int rt, int L, int R, int val){
if(l >= L && r <= R) { cov[rt] = val; return; }
int mid = gmid; pushDown(rt);
if(L <= mid) update(l, mid, lson, L, R, val);
if(R > mid) update(mid + 1, r, rson, L, R, val);
}
int query(int l, int r, int rt, int pos){
if(l == r) return cov[rt];
int mid = gmid; pushDown(rt);
if(pos <= mid) return query(l, mid, lson, pos);
else return query(mid + 1, r, rson, pos);
}
} tr_p, tr_l, tr_tim;
struct ZXTree{
ll sum[maxn * 20], add[maxn * 20];
int ls[maxn * 20], rs[maxn * 20], rt[maxn], tot;
void update(int l, int r, int &rt, int pre, int L, int R, int val){
rt = ++tot, ls[rt] = ls[pre], rs[rt] = rs[pre], sum[rt] = sum[pre], add[rt] = add[pre];
if(l >= L && r <= R){
add[rt] += val;
sum[rt] += val * 1ll * (r - l + 1);
return;
}
int mid = gmid;
if(L <= mid) update(l, mid, ls[rt], ls[pre], L, R, val);
if(R > mid) update(mid + 1, r, rs[rt], rs[pre], L, R, val);
sum[rt] = sum[ls[rt]] + sum[rs[rt]] + add[rt] * 1ll * (r - l + 1);
}
ll query(int l, int r, int rt, int pre, int L, int R){
if(!rt) return 0;
if(l >= L && r <= R) return sum[rt];
int mid = gmid; ll ret = (min(r, R) - max(l, L) + 1) * add[rt];
if(L <= mid) ret += query(l, mid, ls[rt], ls[pre], L, R);
if(R > mid) ret += query(mid + 1, r, rs[rt], rs[pre], L, R);
return ret;
}
} tr_dt;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m >> q;
for(int i = 1; i <= m; ++i){
cin >> len[i];
li[i] = tot + 1;
for(int j = 1; j <= len[i]; ++j){
++tot; cin >> a[tot];
bel[tot] = i;
}
}
for(int d = 1; d <= q; ++d){
tr_dt.rt[d] = tr_dt.rt[d - 1];
int opt, x, y, z; cin >> opt >> x;
if(opt == 1){
cin >> y;
tr_p.update(1, n, 1, y, y + len[x] - 1, y);
tr_l.update(1, n, 1, y, y + len[x] - 1, li[x]);
tr_tim.update(1, n, 1, y, y + len[x] - 1, d);
}
else if(opt == 2){
int p = tr_p.query(1, n, 1, x);
int l = tr_l.query(1, n, 1, x);
int tim = tr_tim.query(1, n, 1, x);
if(!p) { cout << "0\n"; continue; }
int id = l + (x - p);
ll ret = a[id], dt = tr_dt.query(1, tot, tr_dt.rt[tim], tr_dt.rt[0], id, id);
(ret += dt) %= 256;
cout << ret << "\n";
}
else{
cin >> y >> z;
tr_dt.update(1, tot, tr_dt.rt[d], tr_dt.rt[d - 1], li[x] + y - 1, li[x] + z - 1, 1);
}
}
return 0;
}
C - Elven Efficiency
简要题意:
给定 n n n 个数 a i a_i ai,再有 m m m 次操作,每次给定一个数 b i b_i bi,若 a i a_i ai 被 b i b_i bi 整除则需要自增,问最后自增的总次数。
解题思路:
我摊牌了,复杂度不会证明。
参考代码:
#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;
const int maxn = 6e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
vector<int> fac[maxn];
set<int> mul[maxn];
int a[maxn], b[maxn], cnt[maxn];
int n, m;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; ++i) cin >> a[i];
for(int i = 1; i <= m; ++i) cin >> b[i];
for(int i = 2; i < maxn; ++i){
for(int j = i; j < maxn; j += i) fac[j].pb(i);
}
for(int i = 1; i <= n; ++i){
if(!cnt[a[i]]){
for(auto &v : fac[a[i]]) mul[v].emplace(a[i]);
}
++cnt[a[i]];
}
ll ret = 0;
for(int i = 1; i <= m; ++i){
vector<int> vc(sz(mul[b[i]]));
copy(mul[b[i]].begin(), mul[b[i]].end(), vc.begin());
for(auto &v : vc){
ret += cnt[v];
cnt[v + 1] += cnt[v];
cnt[v] = 0;
for(auto &e : fac[v]){
mul[e].erase(v);
}
for(auto &e : fac[v + 1]){
mul[e].emplace(v + 1);
}
}
}
cout << ret << endl;
return 0;
}
D - Swap Free
简要题意:
给定 n n n 个不同的字符串,若两个字符串间仅有两位不同且能其中一个通过交换能得到另一个,则两者连边,求最大独立集。
解题思路:
一次交换操作会改变逆序数奇偶性,将所有字符串按逆序数奇偶分为两类,连边仅在这两类间,故图是二分图,匈牙利跑最大匹配,答案为 n 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;
const int maxn = 5e2 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
vector<int> G[maxn];
string ss[maxn];
int mt[maxn], vis[maxn];
int n;
int dfs(int u){
for(auto &v : G[u]){
if(vis[v]) continue;
vis[v] = 1;
if(!mt[v] || dfs(mt[v])){
mt[v] = u;
return 1;
}
}
return 0;
}
int solve(){
int ret = 0;
for(int i = 1; i <= n; ++i){
memset(vis, 0, sizeof vis);
ret += dfs(i);
}
return ret / 2;
}
int check(const string &s1, const string &s2){
vector<int> vc;
for(int i = 0; i < s1.size(); ++i){
if(s1[i] != s2[i]) vc.pb(i);
}
if(sz(vc) == 2){
int p1 = vc[0], p2 = vc[1];
if(s1[p1] == s2[p2] && s1[p2] == s2[p1]) return 1;
}
return 0;
}
void build(){
for(int i = 1; i <= n; ++i){
for(int j = i + 1; j <= n; ++j){
if(check(ss[i], ss[j])) G[i].pb(j), G[j].pb(i);
}
}
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; ++i) cin >> ss[i];
build();
int ret = n - solve();
cout << ret << endl;
return 0;
}
G - Jumping Path
简要题意:
给定一个 n n n 个结点的树,每个结点带权,求根到叶方向的 L I S LIS LIS 及个数。
解题思路:
序列问题放到树上,若是序列问题,可以先将序列按权值排序,用树状数组维护每个下标结尾的 L I S LIS LIS 和个数,转移则是查询全局最大值和最大值对应的 L I S LIS LIS 个数之和。放到树上,将全局查询变为子树(区间)查询。
参考代码:
#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 = 11092019;
const int inf = 0x3f3f3f3f;
vector<int> G[maxn];
int a[maxn], dfn[maxn], siz[maxn], tp[maxn];
int n, tim;
struct SegTree{
pii mx[maxn << 2];
pii merge(pii x, pii y){
if(x.first == y.first) return {x.first, (x.second + y.second) % mod};
else return max(x, y);
}
void pushUp(int rt){
mx[rt] = merge(mx[lson], mx[rson]);
}
void update(int l, int r, int rt, int pos, pii 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);
}
pii query(int l, int r, int rt, int L, int R){
if(l >= L && r <= R) return mx[rt];
int mid = gmid; pii ret = {0, 0};
if(L <= mid) ret = merge(ret, query(l, mid, lson, L, R));
if(R > mid) ret = merge(ret, query(mid + 1, r, rson, L, R));
return ret;
}
} tr;
void dfs(int u){
dfn[u] = ++tim, siz[u] = 1;
for(auto &v : G[u]){
dfs(v);
siz[u] += siz[v];
}
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i];
for(int i = 2; i <= n; ++i){
int u; cin >> u;
G[u].pb(i);
}
dfs(1);
for(int i = 1; i <= n; ++i) tp[i] = i;
sort(tp + 1, tp + 1 + n, [](int x, int y){
return a[x] != a[y] ? a[x] > a[y] : dfn[x] != dfn[y] ? dfn[x] > dfn[y] : x > y;
});
for(int i = 1; i <= n; ++i){
int u = tp[i];
if(siz[u] == 1){
tr.update(1, n, 1, dfn[u], pii{1, 1});
continue;
}
int l = dfn[u] + 1, r = dfn[u] + siz[u] - 1;
pii tmp = tr.query(1, n, 1, l, r);
if(tmp.first == 0) tmp = {1, 1};
else ++tmp.first;
tr.update(1, n, 1, dfn[u], tmp);
}
pii ret = tr.query(1, n, 1, 1, n);
cout << ret.first << " " << ret.second << endl;
return 0;
}
H - Levenshtein Distance
简要题意:
给定字符集和一个字符串,问能通过一次插入、删除或修改得到的所有字符串。
解题思路:
暴力做,再去重。
参考代码:
#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;
const int maxn = 4e3 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
string a, s; cin >> a >> s;
set<string> st;
for(int i = 0; i <= s.size(); ++i){
for(auto &v : a){
string t = s.substr(0, i) + v + s.substr(i);
st.insert(t);
}
}
for(int i = 0; i < s.size(); ++i){
string t = "";
for(int j = 0; j < s.size(); ++j){
if(j != i) t += s[j];
}
st.insert(t);
for(auto &v : a){
string t = s; t[i] = v;
st.insert(t);
}
}
if(st.count(s)) st.erase(s);
for(auto &v : st) cout << v << endl;
return 0;
}
I - Maze Connect
简要题意:
给定一个网格图,仅包含 \ / . 三种字符,问最少需要删除多少个斜杠字符才能使得没有 . 被包围。
解题思路:
将斜杠看作边建图,利用平面图的欧拉定理,用并查集维护连通性即可。
参考代码:
#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;
const int maxn = 1e3 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
char ss[maxn][maxn];
int id[maxn][maxn], pre[maxn * maxn];
int n, m;
int fin(int x){
return x == pre[x] ? x : pre[x] = fin(pre[x]);
}
int unite(int x, int y){
int fx = fin(x), fy = fin(y);
if(fx == fy) return 0;
pre[fx] = fy;
return 1;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; ++i) cin >> ss[i] + 1;
++n, ++m;
int tot = 0;
for(int i = 0; i <= n; ++i){
for(int j = 0; j <= m; ++j) id[i][j] = ++tot;
}
for(int i = 1; i <= tot; ++i) pre[i] = i;
int ret = 0;
for(int i = 1; i < n; ++i){
for(int j = 1; j < m; ++j){
if(ss[i][j] == '.') continue;
if(ss[i][j] == '/'){
ret += !unite(id[i][j - 1], id[i - 1][j]);
}
else{
ret += !unite(id[i - 1][j - 1], id[i][j]);
}
}
}
cout << ret << endl;
return 0;
}
J - One of Each
简要题意:
给定 n n n 个数,值域为 [ 1 , m ] [1, m] [1,m],保证每个数至少出现一次。求一个字典序最小的 1-m 的排列。
解题思路:
维护一个可选区间 [ l , r ] [l, r] [l,r], r r r 为保证后续解存在的右端点, l l l 为剩余区间的左端点。初始 r r r 就是所有数最后一次出现位置的最小下标。每次在 [ l , r ] [l, r] [l,r] 贪心选择一个最靠前的最小数,然后更新 l l l 和 r r r。实际实现可以用栈做到 O ( n ) O(n) O(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 = 2e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int a[maxn], rm[maxn], vis[maxn];
int n, m;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; ++i){
cin >> a[i];
rm[a[i]] = i;
}
set<int> st_r;
for(int i = 1; i <= m; ++i){
st_r.emplace(rm[i]);
}
st_r.emplace(n + 1);
vector<int> ans;
set<pii> st_l;
int l = 1, r = *st_r.begin();
for(int i = 1; i <= r; ++i){
st_l.emplace(pii{a[i], i});
}
for(int i = 1; i <= m; ++i){
// cout << i << " " << l << " " << r << endl;
pii e = *st_l.begin();
while(vis[e.first]){
st_l.erase(st_l.begin());
e = *st_l.begin();
}
ans.emplace_back(e.first);
vis[e.first] = 1;
while(l <= e.second){
if(st_l.count(pii{a[l], l})) st_l.erase(pii{a[l], l});
++l;
}
st_r.erase(rm[e.first]);
int nr = *st_r.begin();
while(r < nr){
++r;
st_l.emplace(pii{a[r], r});
}
}
for(auto &v : ans) cout << v << " "; cout << endl;
return 0;
}