2024南京icpc区域赛详解与难点解释

2024南京icpc详解与难点解释

  • [E Left Shifting 3](#E Left Shifting 3)
  • [J Social Media](#J Social Media)
  • [K Strips](#K Strips)
  • [M Ordainer of Inexorable Judgment](#M Ordainer of Inexorable Judgment)
  • [B Birthday Gift](#B Birthday Gift)
  • 二叉树

练习地址

本文参考

E Left Shifting 3

签到,最多从前往后移动6位,暴力遍历计算每次的'nanjing'数量,不需要kmp匹配也可以ac。

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
void solve() {
    int n, k;
    cin >> n >> k;
    string s;
    cin >> s;
    if (s.size() < 7) {
        cout << 0 << '\n';
        return;
    }
    int res = 0;
    int cnt = min(6ll, k);
    for (int i = 0; i <= cnt; i++) {
        int ans = 0;
        string s1 = s.substr(0,i);
        string t = s + s1;
        for (int j = i; j + 7 <= t.size(); j++) {
            if (t.substr(j, 7) == "nanjing") {
                ans ++;
            }
        }
        res = max(res, ans);
    }
    cout << res << '\n';
}
signed main() {
    ios_base::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int T;
    cin >> T;
    while (T --) {
        solve();
    }
    return 0;
}

J Social Media

类似微信朋友圈的规则。

有两种情况需要分开考虑

1.要添加的这两个好友有互相间评论过。

不一定是能增加最多的,只是在互相评论的两位中最多。

for (auto [e, w] : edge) {

auto [x, y] = e;

ans = max(ans, deg[x] + deg[y] + w);

}

2.要添加的这两个好友没有互相间评论过。

不知到最多的两个有没有评论过,不重要,只需与互相评论过的且增加评论最多作比较。

ans = max(ans, deg[0] + deg[1]);

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false),cin.tie(nullptr), cout.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        int n, m, k;
        cin >> k >> m >> n;
        vector<int> good(n);
        while (k--) {
            int x;
            cin >> x;
            x--;
            good[x] = 1;
        }
        map<pair<int, int>, int> edge;
        vector<int> deg(n);
        int sum = 0;
        while (m--) {
            int x, y;
            cin >> x >> y;
            x--, y--;
            if (x > y) swap(x, y);
            if (good[x] && good[y]) {
                sum++;
            } else if (x == y) {
                deg[x]++;
            } else if (good[x]) {
                deg[y]++;
            } else if (good[y]) {
                deg[x]++;
            } else {
                edge[{ x, y }]++;
            }
        }
        int ans = 0;
        for (auto [e, w] : edge) {
            auto [x, y] = e;
            ans = max(ans, deg[x] + deg[y] + w);
        }

        sort(deg.begin(), deg.end(), greater<>());
        ans = max(ans, deg[0] + deg[1]);
        ans += sum;
        cout << ans << "\n";
    }
    return 0;
}

K Strips

题目大意:铺地毯,黑地砖的不能铺,红地砖的一定要铺,求可不可以用最少的地毯完成,能得话输出可以的位置。

做法:任意两个黑的地砖之间找红色的,从遇到红色没有被遮住就铺一张,最后可能会铺到黑色(禁区),这时候可以进行平移,平移完成后,如果第一张毯子没有触碰到左黑色,即合法,开始讨论下一个区间。任意一个区间不满足就返回 -1.

我们可以在首尾增加两块黑砖,这样就不用考虑边界问题了。

cpp 复制代码
#include<bits/stdc++.h>
#define int long long 
using namespace std;
typedef vector<int> vi;
typedef pair<int,int> pii;
typedef vector<pii> vii;
void solve() {
    int n, m, k, w;
    cin >> n >> m >> k >> w;
    vii v(n + m + 2);
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        v[i] = make_pair(x, 1);
    }
    for (int i = n; i < n + m; i++) {
        int x;
        cin >> x;
        v[i] = make_pair(x,0);
    }
    v[n + m] = make_pair(w + 1, 0);
    sort(v.begin(), v.end());
    vector<int> ans;
    int l = 0, r = 0;
    for (int i = 0; i < (int)v.size(); i++) {
        if (v[i].second == 1) continue;
        l = r, r = i;
        vi red_idx;
        for (int j = l + 1; j < r; j++) {//两黑之间找红,铺毯子
            red_idx.push_back(v[j].first);
            int tep = v[j].first;
            while (j + 1 < r && v[j + 1].first < tep + k) j++;
        }
        if (red_idx.empty()) continue;
        red_idx.push_back(v[i].first);
        for (int j = (int)red_idx.size() - 2; j >= 0; j--) {
            if (red_idx[j] + k > red_idx[j + 1]) {
                red_idx[j] = red_idx[j + 1] - k;
            }
        }
        if (red_idx[0] <= v[l].first) {
            cout << "-1\n";
            return;
        }
        for (int j = 0; j < (int)   red_idx.size() - 1; j++) {
            ans.push_back(red_idx[j]);
        }
    }
    cout << ans.size() << "\n";
    for (int x : ans) cout << x << " ";
    cout << "\n";
}
signed main() {
    ios_base::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int T;
    cin >> T;
    while (T --) {
        solve();
    }
    return 0;
}

M Ordainer of Inexorable Judgment

计算几何。

答案就是切线角度的最大差值。(必须小于Π)

大于就要特殊处理,让2Π - 最大的空白区域。

这是计算几何的模板,可以实现向量加减法,相等判断,极角计算,线段长度计算等。

cpp 复制代码
int sgn(T x) {
    return (fabs(x) <= eps) ? 0 : (x < 0 ? -1 : 1);
}

struct P {
    T x, y;
    P() : x(0), y(0) {}
    P(T x, T y) : x(x), y(y) {}
    P(T r, double alpha, int _) : x(r * cos(alpha)), y(r * sin(alpha)) {}
    bool operator==(P o) { return sgn(x - o.x) == 0 && sgn(y - o.y) == 0; }
    P operator+(P o) { return P(x + o.x, y + o.y); }
    P operator-(P o) { return P(x - o.x, y - o.y); }
    T operator*(P o) { return x * o.y - y * o.x; }
    T operator^(P o) { return x * o.x + y * o.y; }
    friend double abs(P o) { return sqrt(o.x * o.x + o.y * o.y); }
    double angle() { double res = atan2(y, x); if (sgn(res) < 0) res += (2 * pi); return res; }
};

总代码:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

using T = double;
const double eps = 1e-9, pi = acos(-1.0);
int sgn(T x) {
    return (fabs(x) <= eps) ? 0 : (x < 0 ? -1 : 1);
}

struct P {
    T x, y;
    P() : x(0), y(0) {}
    P(T x, T y) : x(x), y(y) {}
    P(T r, double alpha, int _) : x(r * cos(alpha)), y(r * sin(alpha)) {}
    bool operator==(P o) { return sgn(x - o.x) == 0 && sgn(y - o.y) == 0; }
    P operator+(P o) { return P(x + o.x, y + o.y); }
    P operator-(P o) { return P(x - o.x, y - o.y); }
    T operator*(P o) { return x * o.y - y * o.x; }
    T operator^(P o) { return x * o.x + y * o.y; }
    friend double abs(P o) { return sqrt(o.x * o.x + o.y * o.y); }
    double angle() { double res = atan2(y, x); if (sgn(res) < 0) res += (2 * pi); return res; }
};

int main() {
    int n;
    cin >> n;

    P P0;
    cin >> P0.x >> P0.y;
    double t0 = P0.angle();

    int rad, t;
    cin >> rad >> t;

    vector<double> alpha;  // 存所有的倾斜角
    for (int i = 0; i < n; i++) {
        P Q;
        cin >> Q.x >> Q.y;
        double phi = acos(rad / abs(Q));
        alpha.push_back((Q - P(rad, (Q.angle() + phi), 1)).angle());
        alpha.push_back((Q - P(rad, (Q.angle() - phi), 1)).angle());
    }

    for (int i = 0; i < 2 * n; i++) {
        alpha[i] -= t0;
        if (alpha[i] < 0) alpha[i] += 2 * pi;
    }

    double L = -1, R = -1;
    sort(alpha.begin(), alpha.end());
    bool flag = 1;
    if (alpha[0] + pi > alpha.back()) {//角度小于pi正着算
        L = alpha[0], R = alpha.back();
        flag = 0;
    } else for (int i = 1; i < 2 * n; i++) {//角度大于pi反着算
        if (alpha[i - 1] + pi < alpha[i]) {
            L = alpha[i - 1], R = alpha[i];
        }
    }

    double q = floor(t / (2 * pi));  
    double r = fmod(t, (2 * pi));    
    double ans = q * (R - L);  
    if (r > L) ans += r - L;
    if (r > R) ans -= r - R;  //加多了,减掉多余的 

    cout << fixed << setprecision(12);
    if (flag) {
        cout << (t - ans) << '\n';
    } else {
        cout << ans << '\n';
    }

    return 0;
}

B Birthday Gift

思维题,无论如何,奇数位上的0 ,1一定可以和偶数位上的0,1消除。

cpp 复制代码
#include<bits/stdc++.h> 
#define int long long
const int N = 2e5 + 5;
using namespace std;
void solve(){
    string s;
    cin >> s;
    int a[2][2] = {0};
    int cnt2 = 0;
    for(int i = 0; i < (int)s.size(); i++){
        if(s[i] == '2'){
            cnt2 ++;
        }else{
           a[i&1][s[i] - '0'] ++; 
        }
    }
    int cnt1 = abs(a[1][1] - a[0][1]);
    int cnt0 = abs(a[1][0] - a[0][0]);

    int tep = min(cnt1, cnt2);
    cnt1 -= tep;
    cnt2 -= tep;

    tep = min(cnt0, cnt2);
    cnt2 -= tep;
    cnt0 -= tep;

    if(cnt2 == 0){
        cout << cnt1 + cnt0 << '\n';
    }else{
        cout << cnt2 % 2 << '\n';
    }
}
signed main( ){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int T;
    cin >> T;
    while(T --){
        solve();
    }
    return 0;
}

二叉树

每次询问都要至少剪枝一半才可以保证在log2(n)次数内找到所需点。

于是,可以考虑树的重心,直到最后只剩下一个点极为答案。

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
void solve() {
    int n;
    cin >> n;
    auto query = [&](int x, int y) {
        cout << "? " << ++x << " " << ++y << endl;
        cin >> x;
        return x;
    };

    auto answer = [&](int x) {
        cout << "! " << ++x << endl;
    };

    //建图
    vector<vector<int>> E(n);
    for (int i = 0; i < n; i++) {
        int x, y;
        cin >> x >> y;
        x--, y--;
        if (x != -1) E[i].push_back(x), E[x].push_back(i);
        if (y != -1) E[i].push_back(y), E[y].push_back(i);
    }

    int s = 0;
    while (1) {
//--------------------树的重心求法-----------------------//
        vector<int> siz(n), mss(n);
        auto dfs = [&](auto && self, int u, int fa) -> void{//这里void必须写,因为推断不出返回值
            siz[u] = 1;
            for (auto v: E[u]) {
                if (v != fa) {
                    self(self, v, u);
                    siz[u] += siz[v];
                    mss[u] = max(mss[u], siz[v]);
                }
            }
        };
        dfs(dfs, s, -1);

        int ctr;//重心
        int tot = siz[s];
        for (int u = 0; u < n; u++) {
            mss[u] = max(mss[u], tot - siz[u]);//上面的子树节点数
            if (siz[u] && mss[u] <= tot / 2) ctr = u;
        }
//-------------------------------------------------------------//
        if (E[ctr].empty()) {
            answer(ctr);
            return;
        } else if (E[ctr].size() == 1) {
            int x = query(ctr, E[ctr][0]);
            if (x == 0) {
                answer(ctr);
                return;
            } else {
                answer(E[ctr][0]);
                return;
            }
        } else {
            sort(E[ctr].begin(), E[ctr].end(), [&](const int u, const int v) {
                return mss[u] < mss[v];
            });//排序重心节点的子树

            int x = query(E[ctr][0], E[ctr][1]);
            if (x == 0) {  // d(u) < d(v)
                s = E[ctr][0];
                E[s].erase(find(E[s].begin(), E[s].end(), ctr));//把从s到ctr(重心)的边删除,实现剪枝。
            } else if (x == 2) {
                s = E[ctr][1];
                E[s].erase(find(E[s].begin(), E[s].end(), ctr));
            } else {
                s = ctr;
                E[s].erase(E[s].begin(),E[s].begin() + 2);//删除前两条边剪枝,左闭右开
            }
        }
    }
}

signed main() {
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int T;
    cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}
相关推荐
YxVoyager10 小时前
Qt C++ :XML文件处理工具 <QXml>模块
xml·c++·qt
一只鱼^_10 小时前
力扣第470场周赛
数据结构·c++·算法·leetcode·深度优先·动态规划·启发式算法
greentea_201315 小时前
Codeforces Round 65 A. Way Too Long Words(71)
c++
Overboom17 小时前
[C++] --- 常用设计模式
开发语言·c++·设计模式
Univin17 小时前
C++(10.4)
开发语言·数据结构·c++
YxVoyager17 小时前
Qt C++ :QLayout 布局管理
c++·qt
KyollBM17 小时前
每日羊题 (质数筛 + 数学 | 构造 + 位运算)
开发语言·c++·算法
Univin19 小时前
C++(10.5)
开发语言·c++·算法
AA陈超19 小时前
虚幻引擎UE5专用服务器游戏开发-33 在上半身播放组合蒙太奇
c++·游戏·ue5·游戏引擎·虚幻