2.10周报
一、本周内容总结
本周主要训练了3场cf,2场天梯赛,1场牛客,剩余时间就是补题
cf的训练还是有很大的提升空间,基本都是3题卡在第4题调试差点就出了,写题的速度要提高
天梯赛第一场写的还可以260分,写的时候基本没有出现什么差错,尽可能的拿了分。第二场打的挺差165分,问题还是模拟的能力不太行,应该想清楚再下手的,特别是对于处理起来很复杂的题,没有把握的情况下手就很浪费时间
最后一场牛客是6场里状态最好的一场,排名114,首先这场其实写的是挺慢的,很多题都是在后期过的,想题的时候并没有着急,好在心态平稳结果也是好的
还有一周的训练,下周就主要练好蓝桥和个人赛,继续加油
二、本周补题
2.10 2小时个人赛
A-Vasya and Book
思路:每步可以跳d,跳到d+x, 2d+x, ..., kd+x,即当x≡y(mod d)时可以相互抵达。
并且端点1和n一定可以到达,分别计算几种情况需要的次数取最小。
void solve() {
    int n, x, y, d;
    cin >> n >> x >> y >> d;
    int ans = LLONG_MAX;
    if (x % d == y % d) {
        ans = abs((x / d) - (y / d));
    }
    if (y % d == 1) {
        int l = 0, r = 0;
        if (x != 1) l += (x - 1 + d - 1) / d;
        if (y != 1) r = y / d;
        ans = min(ans, l + r);
    }
    if (y % d == n % d) {
        int l = 0, r = 0;
        if (x != n) r = (n - x + d - 1) / d;
        if (y != n) l = (n / d) - (y / d);
        ans = min(ans, l + r);
    }
    if (ans == LLONG_MAX) cout << "-1\n";
    else cout << ans << '\n';
}
B-Vova and Trophies
void solve() {
    int n;
    string s;
    cin >> n >> s;
    int all = 0;
    for (int i = 0; i < n; ++i)
        if (s[i] == 'G') all ++;
//    cout << all << '\n';
    int gg = 0, ss = 0, ans = 0;
    for (int i = 0, l = 0, pre = -1; i < n; ++i) {
        if (s[i] == 'G') gg ++;
        else {
           if (ss) {
               ans = max(ans, gg + (all != gg));
               while (l <= pre) {
                   if (s[l ++] == 'G') gg --;
               }
               pre = i;
           } else ss = 1, pre = i;
        }
    }
    ans = max(ans, gg + (all != gg));
    cout << ans;
}
C-Multi-Subject Competition
void solve() {
    int n, m;
    cin >> n >> m;
    vector<vector<int> > ve(m + 1);
    for (int i = 1; i <= n; ++i) {
        int x, y;
        cin >> x >> y;
        ve[x].push_back(y);
    }
    int ans = 0;
    for (int i = 1; i <= m; ++i) {
        if (ve[i].empty()) continue;
        std::sort(ve[i].begin(), ve[i].end(), greater<>());
        for (int j = 1; j < ve[i].size(); ++j) ve[i][j] += ve[i][j - 1];
    }
    int idx = 1, sum = 0;
    queue<int> q;
    for (int i = 1; i <= m; ++i) {
        if (ve[i].size() && ve[i][0] > 0) q.push(i), sum += ve[i][0];
    }
    ans = max(ans, sum);
    while (q.size()) {
        queue<int> g;
        while (q.size()) {
            int t = q.front();
            q.pop();
            sum -= ve[t][idx - 1];
            if (ve[t].size() - 1 >= idx && ve[t][idx] > 0) g.push(t), sum += ve[t][idx];
        }
        ans = max(ans, sum);
        idx ++;
        q = g;
    }
    cout << ans;
}
D-Maximum Diameter Graph
void solve() {
    int n;
    cin >> n;
    vector<int> d(n + 1);
    int all = 0;
    for (int i = 1; i <= n; ++i) cin >> d[i], all += d[i];
    if (2 * (n - 1) > all) cout << "NO\n";
    else {
        vector<int> ve, ans;
        for (int i = 1; i <= n; ++i) {
            if (d[i] == 1) ve.push_back(i);
            else ans.push_back(i);
        }
        int cnt = ans.size() + min((int)ve.size(), 2ll) - 1;
        int ed = ans.size() - 1 + ve.size();
        cout << "YES " << cnt << '\n';
        cout << ed << '\n';
        int idx = 0;
        if (!ans.empty() && idx < ve.size()) cout << ans.back() << ' ' << ve[idx ++] << '\n';
        for (int i = 0; i < ans.size(); ++i) {
            if (i > 0) cout << ans[i] << ' ' << ans[i - 1] << '\n';
            int dd = d[ans[i]];
            if (i - 1 >= 0) dd --;
            if (i + 1 < ans.size()) dd --;
            while (idx < ve.size() && dd > 0) {
                cout << ans[i] << ' ' << ve[idx ++] << '\n';
                dd --;
            }
        }
    }
}
2.11 寒假牛客6
9/12,rank114
这场的难度算正常,偏思维没用到很难的算法基本都可以做出来。问题还是写题比较慢,好在心态比较平稳,没有放弃orz
A
void solve() {
    int n;
    cin >> n;
    vector<int> a(n);
    int ans = 0;
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
        if (i && a[i] != a[i - 1]) ans ++;
    }
    cout << ans + 1 << '\n';
}
K
思路:已知2x+1=y,得到x=(y-1)/2,翻页x加二或减二,即同奇偶
void solve() {
    int x, y;
    cin >> x >> y;
    int c = y - 1;
     
    if (c % 2 == 0) {
        c /= 2;
        if (x % 2 == c % 2) cout << "YES\n";
        else cout << "NO\n";
    } else {
        cout << "NO\n";
    }
}
L
思路:每次可以删2个,且CHICKEN为子序列,说明原序列长度为奇数
提取出CHICKEN后,对于需要删除的字符须保证出现次数最多的字符数量不能超过总数量
void solve() {
    int n;
    string s;
    cin >> n >> s;
    if (n % 2 == 0) cout << "NO\n";
    else {
        string t = "CHICKEN";
        int idx = 0;
        vector<int> cnt(26);
        for (int i = 0; i < n; ++i) {
            if (idx < t.size() && s[i] == t[idx]) idx ++;
            else cnt[s[i] - 'A'] ++;
        }
        if (idx < t.size()) cout << "NO\n";
        else { 
            int ma = 0, all = 0;
            for (int i = 0; i < 26; ++i) {
                all += cnt[i];
                ma = max(ma, cnt[i]);
            }
            if (ma > all / 2) cout << "NO\n";
            else cout << "YES\n";
        }
    }
}
C
思路:打表,可以发现少了2i,比较简单的写法:二分答案,判断x-(不超过x的2i的个数)≤ k
还可以发现新数列的相邻数差值为2,4,4,2,2,4,2,2,2,2,2,2,4,2...每相邻4之间的长度为2i
void solve() {
    int k;
    cin >> k;
    if (k == 1) cout << 2 << '\n';
    else if (k == 2) cout << 6 << '\n';
    else {
        int ans = 6;
        int now = 1;
        k --;
        for (int i = 2; now + i - 1 <= k; i *= 2) {
            ans += 4;
            ans += (i - 2) * 2;
            now += i - 1;
        }
        if (now < k) ans += (k - now) * 2;
        cout << ans << '\n';
    }
}
I
思路:对于一个询问[l,r,c]相当于求 [1~n] 中小于c的个数 - [1~l-1] 和 [r+1~n] 中小于c的个数
可以分别求[1~i]小于c的个数和[i~n]小于c的个数,用树状数组维护
int n;
vector<int> c(N);
 
int lowbit(int x){
    return x&(-x);
}
 
void add(int i,int k){    //在i位置加上k
    while(i <= n){
        c[i] += k;
        i += lowbit(i);
    }
}
 
int getsum(int i){        //求A[1 ~ i]的和
    int res = 0;
    while(i > 0){
        res += c[i];
        i -= lowbit(i);
    }
    return res;
}
 
int get(int x, int y) {     //求A[x ~ y]的和
    if (y < x) return 0;
    return getsum(y) - getsum(x - 1);
}
 
struct E {
    int id, l, r, c;
};
 
bool cmp1(E a, E b) {
    return a.l < b.l;
}
bool cmp2(E a, E b) {
    return a.r > b.r;
}
void solve() {
    int m;
    cin >> n >> m;
    vector<int> ve(n + 1);
 
    for (int i = 1; i <= n; ++i) cin >> ve[i];
    vector<E> g(m);
    vector<int> ans(m);
    for (int i = 0; i < m; ++i) {
        cin >> g[i].l >> g[i].r >> g[i].c;
        ans[i] = g[i].l + ve[g[i].c] - 1;
        g[i].id = i;
    }
    std::fill(c.begin(), c.end(), 0);
    sort(g.begin(), g.end(), cmp1);
    for (int i = 0, l = 0; i < m; ++i) {
        while (l + 1 <= g[i].l - 1) {
            add(ve[++l], 1);
        }
        int cnt = get(1, ve[g[i].c] - 1);
        ans[g[i].id] -= cnt;
    }
    sort(g.begin(), g.end(), cmp2);
    std::fill(c.begin(), c.end(), 0);
    for (int i = 0, r = n + 1; i < m; ++i) {
        while (r - 1 >= g[i].r + 1) {
            add(ve[--r], 1);
        }
        int cnt = get(1, ve[g[i].c] - 1);
        ans[g[i].id] -= cnt;
    }
    for (int i = 0; i < m; ++i) cout << ans[i] << '\n';
}
J
思路:首先开始一直磨刀是最优的,前y回合都执行磨刀,对于砍刀,可以发现一定是从某一回合开始一直砍,直到回合结束或刀坏为止是最优的
枚举从那一回合开始砍刀,发现y回合后都不会进行磨刀所以砍刀的情况都是一样的,所以枚举到y就可以
void solve() {
    int n, x, y;
    cin >> n >> x >> y;
    int ans = 0;
    for (int i = 1, now = x; i <= y && i <= n; ++i) {
        int res = 0;
        now ++;
        int pre = now - 1;
        int lev = min(y - i + 1, n - i + 1);
        res += lev * now;
        int cnt = min(n - (i - 1 + lev), pre);
        res += cnt * pre - (cnt * (cnt - 1) / 2);
        ans = max(ans, res);
    }
    cout << ans << '\n';
}
B
思路:dp,f[i][0/1]表示前i局且第i局比赛为(a,b)或(b,a)的最小代价
void solve() {
    int n, c1, c2;
    cin >> n >> c1 >> c2;
    vector<E> ve(n + 5);
    for (int i = 1; i <= n; ++i) cin >> ve[i].a >> ve[i].b;
    vector<vector<int> > f(n + 5, vector<int>(2, 1e15));
    f[0][0] = f[0][1] = 0;
    ve[0] = {0, 0};
    ve[n + 1] = {INT32_MAX, INT32_MAX};
    for (int i = 1; i <= n + 1; ++i) {
        for (int j = 0; j < i; ++j) {
            int add = (i - j - 1) * c1;
            if (ve[i].a >= ve[j].a && ve[i].b >= ve[j].b) f[i][0] = min(f[i][0], f[j][0] + add), f[i][1] = min(f[i][1], f[j][1] + c2 + add);
            if (ve[i].a >= ve[j].b && ve[i].b >= ve[j].a) f[i][0] = min(f[i][0], f[j][1] + add), f[i][1] = min(f[i][1], f[j][0] + c2 + add);
        }
    }
    cout << min(f[n + 1][0], f[n + 1][1]) << '\n';
}
H
思路:长度为偶数的时候输出n,n-1,...,3,2,1
奇数的时候将倒序序列两两交换
void solve() {
    int n, m;
    cin >> n >> m;
    int ok = 0;
    for (int i = 0; i < m; ++i) {
        int l, r, c;
        cin >> l >> r >> c;
        ok = (r - l) % 2;
    }
    if (!ok) {
        for (int i = n; i >= 1; i -= 2) {
            if (i == 1) cout << i;
            else {
                cout << i - 1 << ' ' << i << ' ';
            }
        }
        cout << '\n';
    } else {
        for (int i = n; i >= 1; --i) cout << i << ' ';
        cout << '\n';
    }
}
F
思路:首先按a从小到大排序,可以看出后一项的a可以推出前一项的a+b
考虑如何获得最后一项a+b,对于第i项的a+b可以由第i-1项之前的所有a+b推出,因为选择第i-1项的a后,第i-1项之前的a+b都可以推出
void solve() {
    int n;
    cin >> n;
    vector<E> ve(n);
    for (int i = 0; i < n; ++i) cin >> ve[i].a >> ve[i].b;
    sort(ve.begin(), ve.end());
    int ans = 0;
    vector<int> ma(n + 5, 0), ok (n + 5, 0);
    for (int i = 0; i < n; ++i) {
        if (i < n - 1) ans = max(ans, ve[i].a + ve[i].b);
        if (i == 0) ma[i] = ve[i].a + ve[i].b;
        else ma[i] = max(ma[i - 1], ve[i].a + ve[i].b);
        if (i - 2 >= 0 && ma[i - 2] >= ve[i].a) ok[i] = 1;
        if (i > 0 && ve[i - 1].a >= ve[i].a) ok[i] = 1;
        if (i > 0 && ok[i - 1] && (ve[i - 1].a + ve[i - 1].b) >= ve[i].a) ok[i] = 1;
    }
    if (ok[n - 1]) ans = max(ans, ve[n - 1].a + ve[n - 1].b);
    ans = max(ans, ve[n - 1].a);
    cout << ans << '\n';
}
2.12 2小时个人赛
A - Li Hua and Maze
思路:两个位置一定不相邻,阻拦其中一个位置的路径即可,最多4次,还要计算在边界的情况
void solve() {
    int n, m;
    cin >> n >> m;
    int x[2], y[2];
    for (int i = 0; i < 2; ++i) cin >> x[i] >> y[i];
    int ans = 4;
    for (int i = 0; i < 2; ++i) {
        int c = 0;
        for (int j = 0; j < 4; ++j) {
            int xx = x[i] + dx[j], yy = y[i] + dy[j];
            if (xx >= 1 && xx <= n && yy >= 1 && yy <= m) c ++;
        }
        ans = min(ans, c);
    }
    cout << ans << '\n';
}
B - Li Hua and Pattern
思路:要求翻转180度后图形相同,直接枚举翻转前后不同的位置个数,由于是前后部分是对应的,当两个对应的位置不一样,只需要翻转一次即可,那只需统计前一半部分不同的个数,特别的,当n为奇数时,中间一行是自己进行翻转对应,对于这一行只需要统计前半部分列
当需要的次数不超过k,差值为偶数时一定可以,因为可以对某一位置来回翻转还原。差值为奇数时只有当n为奇数时一定可以,因为最中心的位置无论是什么都满足
void solve() {
    int n, k;
    cin >> n >> k;
    vector<vector<int>> ve(n, vector<int>(n));
    vector<vector<int>> g(n, vector<int>(n));
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            cin >> ve[i][j];
            g[i][j] = ve[i][j];
        }
        std::reverse(g[i].begin(), g[i].end());
    }
    int cnt = 0;
    for (int i = 0, j = n - 1; i < (n + 1) / 2; ++i, --j) {
        int m = n;
        if (n % 2 == 1 && i == n / 2) m = n / 2;
        for (int z = 0; z < m; ++z) {
            if (g[i][z] != ve[j][z]) cnt ++;
        }
    }
    if (cnt <= k) {
        if (((cnt - k) % 2 == 0) || n % 2) cout << "YES\n";
        else cout << "NO\n";
    }
    else cout << "NO\n";
}
C - Li Hua and Chess
思路:首先对(1,1)操作,可以知道目标位置到边界的距离的最大值x,再对(1,1+x)和(1+x,1)操作,可以知道目标位置到边界距离的最小值y,知道了到边界的距离相当于知道了坐标
void solve() {
    int n, m;
    cin >> n >> m;
    cout << "? " << 1 << ' ' << 1 << '\n';
    int c;
    cin >> c;
    int x = LLONG_MAX, y = LLONG_MAX;
    if (1 + c <= n) {
        cout << "? " << 1 + c << ' ' << 1 << '\n';
        cin >> x;
    }
    if (1 + c <= m) {
        cout << "? " << 1 << ' ' << 1 + c << '\n';
        cin >> y;
    }
    if (x < y) cout << "! " << 1 + c << ' ' << 1 + x << '\n';
    else cout << "! " << 1 + y << ' ' << 1 + c << '\n';
}
D - Li Hua and Tree
思路:
首先用dfs预处理出所有子树的大小cnt,子树的值sum,节点的父节点fa,用优先队列维护每个节点的儿子节点的(子树大小,儿子节点编号)
对于操作1,直接输出sum
对于操作2,首先找到父节点,及重子(优先队列队首),更新所有相关的值,(注意到没有办法将x从父节点的队列中删除,也不能将重子从x的队列中删除,可以根据节点的父节点fa来判断,取重子时,排除掉父节点不等于x的儿子节点)
struct E{
    int d, id;
    bool operator<(const E & e) const {
        if (d != e.d) return d < e.d;
        return id > e.id;
    }
};
vector<int> a(N);
vector<int> sum(N), d(N), faa(N, -1);
priority_queue<E> q[N];
vector<vector<int>> ve;
void solve() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    ve = vector<vector<int>> (n + 1);
    for (int i = 1; i < n; ++i) {
        int u, v;
        cin >> u >> v;
        ve[u].push_back(v), ve[v].push_back(u);
    }
 
    auto dfs = [&] (auto dfs, int u, int fa) -> void {
        sum[u] = a[u], d[u] = 1;
        for (auto v:ve[u]) {
            if (v == fa) continue;
            faa[v] = u;
            dfs(dfs, v, u);
            sum[u] += sum[v], d[u] += d[v];
            q[u].push({d[v], v});
        }
        return ;
    };
    dfs(dfs, 1, -1);
    for (int i = 0; i < m; ++i) {
        int op, x;
        cin >> op >> x;
        if (op == 1) {
            cout << sum[x] << '\n';
        } else {
            while (q[x].size() && faa[q[x].top().id] != x) q[x].pop();
            if (q[x].empty()) continue;
            int son = q[x].top().id;
            q[x].pop();
            int t = d[son];
            d[son] = d[x];
            q[faa[x]].push({d[son], son});
            d[x] -= t;
            q[son].push({d[x], x});
            t = sum[son];
            sum[son] = sum[x];
            sum[x] -= t;
            faa[son] = faa[x], faa[x] = son;
        }
    }
}
2.14 2小时个人赛
这场打的div2,前3题比较简单,主要卡在E了,做法不是很难,需要优化,考虑到需要记忆化,但是最坏的情况需要记录n2,所以需要考虑到分治,只记录节点数小于√n的深度的节点对。试了下umap/map也会被卡,所以最后考虑离散化,用1e5*500的数组记录可以过
A - Walking Master
思路:只有一个操作可以同时向上和向右,一个操作只能向左,说明操作次数是固定的,直接计算即可
void solve() {
    int a, b, c, d;
    cin >> a >> b >> c >> d;
    if (d < b) cout << "-1\n";
    else {
        int s = d - b;
        a += s;
        if (c > a) cout << "-1\n";
        else {
            s += a - c;
            cout << s << '\n';
        }
    }
}
B - Mex Master
思路:分类讨论
答案为0,不能存在相邻的0,判断是否能分隔所有的0
答案为1,在不能分隔所有0的情况下,不能存在相邻的0和1,判断是否存在大于1的数,将其插在0和1中间即可,如0000X11111
答案为2,此时一定不存在大于1的数,且以上情况都不满足,如0001,答案一定为2
void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 1);
    int all = 0, cnt = 0, ok = 0;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        if (a[i] != 0) all ++;
        if (a[i] > 1) cnt ++;
        if (a[i] == 1) ok = 1;
    }
    int a0 = n - all;
    if (a0 <= all + 1) cout << "0\n";
    else {
        if (cnt >= 1 || ok == 0) cout << "1\n";
        else cout << "2\n";
    }
 
}
C - Sequence Master
思路:只存在几种方案
1. 全0
3. 当n为1时,两个数相等,即答案为两数的差
3. 但n为2时,可以全为2
4. 当n为偶数时,可以构造出一个n,剩余全为-1
void solve() {
    int n;
    cin >> n;
    vector<int> ve(2 * n + 1);
    for (int i = 1; i <= 2 * n; ++i) cin >> ve[i];
    if (n == 1) {
        cout << abs(ve[1] - ve[2]) << '\n';
    } else {
        std::sort(ve.begin() + 1, ve.end());
        vector<vector<int> > ans;
        vector<int> g;
        if (n == 2) {
            for (int i = 1; i <= 2 * n; ++i) {
                g.push_back(2);
            }
            ans.push_back(g);
        }
 
        if (g.size()) g.clear();
        for (int i = 1; i <= 2 * n; ++i) {
            g.push_back(0);
        }
        ans.push_back(g);
 
        if (n % 2 == 0) {
            if (g.size()) g.clear();
            for (int i = 1; i <= 2 * n; ++i) {
                if (i == 2 * n) g.push_back(n);
                else g.push_back(-1);
            }
            ans.push_back(g);
        }
 
        int res = LLONG_MAX;
        for (int i = 0; i < ans.size(); ++i) {
            int c = 0;
            for (int j = 1; j <= 2 * n; ++j) {
//                cout << ans[i][j - 1] << ' ';
                c += abs(ve[j] - ans[i][j - 1]);
            }
//            cout << '\n';
            res = min(res, c);
        }
        cout << res << '\n';
    }
}
E - Tree Master
思路:首先考虑记搜计算答案,最多有n2对,考虑分治,记录节点个数小于√n的深度的节点对,会被卡常,对每个深度的节点离散化,用1e5*500的数组就可以记录
vector<vector<ll> > mp(N, vector<ll>(500));
vector<int> a(N), p(N), dep(N), cnt(N), id(N);
vector<vector<int> > ve(N);
void solve() {
    int n, q;
    cin >> n >> q;
 
 
    for (int i = 1; i <= n; ++i) cin >> a[i];
    for (int i = 2; i <= n; ++i) cin >> p[i], ve[p[i]].push_back(i);
 
    auto dfs = [&](auto dfs, int u) -> void {
        cnt[dep[u]] ++;
        id[u] = cnt[dep[u]];
        for (auto v: ve[u]) {
            dep[v] = dep[u] + 1;
            dfs(dfs, v);
        }
    };
    dfs(dfs, 1);
    int m = (int)::sqrt(n) + 5;
    auto go = [&](auto go, int x, int y) -> ll {
        if (x > y) swap(x, y);
        if (cnt[dep[x]] < m && mp[x][id[y]]) return mp[x][id[y]];
        if (x == 0 && y == 0) return 0ll;
        ll c = 1ll * a[x] * a[y] + go(go, p[x], p[y]);
        if (cnt[dep[x]] < m) mp[x][id[y]] = c;
        return c;
    };
 
    for (int i = 0; i < q; ++i) {
        int x, y;
        cin >> x >> y;
        cout << go(go, x, y) << '\n';
    }
 
}
2.15 寒假天梯训练5
这场打了165,坑点有很多,写模拟被卡了很久,特别是L1-8估值一亿的AI核心代码,细节很多,赛时的时候删删改改最后也没写完,补题的时候发现写过这道题的题解,看了之后发现按种类分类确实很方便。L2-2冰岛人同样被卡了,刚开始担心内存会开不够就省略了很多数组,全用一个数组来存,最后就是写的头都大了。
                    
                
                
            
        
浙公网安备 33010602011771号