North American Southeast Regional 2019 (Div 1) 补题

题目链接

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(1n1025),求满足 a ∗ a = n a*a=n aa=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+ki1] 位置;② 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;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值