The 2022 ICPC Asia Nanjing Regional Contest IGDA,和令人疑惑的M

lulaalu發表於2024-10-19

I - 完美迴文

題意

把單詞改成一串相同的字母,最小修改次數

思路

把所有字母改成這個單詞中出現次數最多的字母

程式碼

#include <bits/stdc++.h>
using namespace std;

void solve() {
    string s;
    map<char, int> mp;
    cin >> s;
    int mx = 0;
    for (char ch : s) {
        mp[ch]++;
        mx = max(mx, mp[ch]);
    }
    cout << s.size() - mx << '\n';
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) solve();
    return 0;
}

G - 邪惡銘刻

題意

按順序有三種情況

1獲取野獸,攻擊力為1
-1合成兩隻野獸
0選擇1或-1

求最後野獸的平均戰力最高是多少

思路

貪心的考慮0應該怎麼分配

由於算的是平均值,合成野獸的貢獻比增加野獸的貢獻大,那麼儘量選-1,只剩一隻野獸的時候再選1

但是如果出現\(1,1,1,0,-1,-1,\) 這種序列,遇到0取-1是無法完成所有事件的

那麼可以把0位置記錄下來,一旦碰到無法完成所有任務的情況,就把0從-1改成1

程式碼

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'

void solve() {
    int n;
    cin >> n;
    vector<int> a(n);
    for (int &i : a) cin >> i;
    int cnt = 0;
    int p = 1, q = 1;
    for (int i = 0; i < n; i++) {
        if (a[i] == 1) {
            p++;
            q++;
        } else if (a[i] == -1) {
            if (q > 1) {
                q--;
            } else {
                if (cnt) {
                    p++, q++;
                    cnt--;
                } else {
                    cout << -1 << endl;
                    return;
                }
            }
        } else {
            if (q > 1) {
                q--;
                cnt++;
            } else {
                p++, q++;
            }
        }
    }
    int z = __gcd(p, q);
    cout << p / z << " " << q / z << endl;
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) solve();
    return 0;
}

D - 聊天程式

題意

給初始序列 \(a\),要求最大化 \(a\) 的第 \(k\) 大的數

可以對 \(a\) 進行如下操作,給定等差數列的長度\(m\),首項\(c\),公差\(d\)

選擇 \(a\) 的連續序列,把等差數列加上去

最多加1次

思路

採用二分第k大的值

關鍵在於check函式怎麼寫

暴力就是列舉每個位置的 \(a_i\) 作為開頭,加一遍後看看能不能滿足第 \(k\) 大的數大於 \(x\)

即便是暴力方式,也不能每次新建一個陣列來排序,我們選擇用計數的方式

大於等於 \(x\) 的個數大於等於 \(k\) 說明滿足要求(注意這裡的第k大是指從大到小排序的第k個數字,不用去重

那麼接下來思考最佳化,對於等差數列,我們先只觀察一個位置 以\([1,1,4,5,1,4]\)為例

\(c=1,d=2,m=3\),那麼數列就是\(1,3,5\)

假定二分的第k大的值是4,觀察 \(a_4=1\) ,如果 \(a_4\) (下標從0開始)作為等差數列的第二項,是滿足要求的

但是如果作為等差數列的第3項,加起來也比4大

那麼列舉 \(a_2\) 為開頭,從 \(a_2\)\(a_4\) ,排除原本就大於4的值,現在對答案的貢獻就是 \(a_4\) 位置的1個

列舉 \(a_3\) 為開頭,從 \(a_3\)\(a_5\),對答案的貢獻還是 \(a_4\)

每個位置作為等差開頭從而新增的大於 \(x\) 的數量加上原本就大於 \(x\) 的數量如果大於等於 \(k\),return true

差分維護

程式碼

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'

const int N = 2e5 + 10;
int a[N];
int n, k, m, c, d;
int sum[N];
bool check(int x) {
    fill_n(sum, n + 1, 0);
    int cnt = 0;
    for (int i = 0; i < n; i++) {
        if (a[i] < x) {
            int rk;
            if (d != 0) {
                if (x <= a[i] + c)
                    rk = 1;
                else
                    rk = (x - a[i] - c + d - 1) / d + 1;
                if (rk > m) continue;
                if (i - rk + 1 >= 0) sum[i - rk + 1]++;
                if (i - m >= 0) sum[i - m] -= 1;
            } else {
                if (x - a[i] - c <= 0) {
                    rk = 0;
                    sum[i]++;
                    if (i - m >= 0) {
                        sum[i - m] -= 1;
                    }
                }
            }
        } else {
            cnt++;
        }
    }

    for (int i = n - 1; i >= 0; i--) {
        sum[i] += sum[i + 1];
        if (cnt + sum[i] >= k) return 1;
    }

    return 0;
}

void solve() {
    cin >> n >> k >> m >> c >> d;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    int l = 0, r = 1e18;
    int ans = 0;
    while (l <= r) {
        int mid = l + r >> 1;
        if (check(mid)) {
            l = mid + 1;
            ans = mid;
        } else
            r = mid - 1;
    }
    cout << ans;
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0);
    int t = 1;
    while (t--) {
        solve();
    }
    return 0;
}

A - 停停,昨日請不要再重現

題意

一個棋盤上站滿了袋鼠,給操作序列,袋鼠跟隨操作序列移動,如果跳出棋盤就移除

棋盤上有個洞,掉進洞的袋鼠也被移除

問最後剩下 \(k\) 只袋鼠的話,有多少可能的位置上有洞,每次棋盤上只有一個洞

思路

在沒有洞的情況下,可以判斷出最後剩下的袋鼠是一個固定的矩形部分\(n',m'\)

在有洞的情況下,可以看成 \(n'm'\) 固定,這樣洞就有一個路徑

並且洞的路徑不會超,洞的路徑與操作相反,L說明洞往右走

而如果洞的路徑超了,說明袋鼠全都走出去了,判掉就行

也就是說,我們把洞的路徑看成一個方形g,用[[二維差分]]維護一下

\(g[x][y]\) 表示在 \((x,y),(x-U,y-L)\) 這個矩形內的洞經過的格子數

用所有剩下的袋鼠 \(x-g[x][y]\) 就是剩餘的袋鼠數

程式碼

程式碼抄襲參考這篇

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N = 1000 + 10;
int n, m, k;
int U, D, L, R, U_, D_, L_, R_;
bool st[N][N]; // stone,表示石頭是否存在在這一格
int g[N][N];
string op;

void add(int x1, int y1, int x2, int y2) {
    // 去重,洞走過多次等同於走一次
    if (st[x1][y1]) return;
    st[x1][y1] = true;
    // 差分
    g[x1][y1]++;
    g[x2 + 1][y1]--;
    g[x1][y2 + 1]--;
    g[x2 + 1][y2 + 1]++;
}

void solve() {
    cin >> n >> m >> k >> op;
    // 左上角為座標原點
    U_ = L_ = U = L = 1;
    R_ = R = m;
    D_ = D = n;
    memset(st, 0, sizeof st);
    memset(g, 0, sizeof g);

    // 確定邊界
    for (char ch : op) {
        // 往左,說明左邊兩行不用了
        if (ch == 'L') L_++, R_++;
        if (ch == 'R') L_--, R_--;
        if (ch == 'U') U_++, D_++;
        if (ch == 'D') U_--, D_--;
        L = max(L, L_);
        R = min(R, R_);
        U = max(U, U_);
        D = min(D, D_);
    }

    // 無袋鼠剩餘
    if (U > D || L > R) {
        // 如果k>0 不可能完成,反之,洞隨便放
        if (k)
            cout << "0\n";
        else
            cout << n * m << endl;
        return;
    }

    // 統計袋鼠經過格子的情況
    int x = (D - U + 1) * (R - L + 1), cnt = 0;
    add(U, L, D, R);
    for (char ch : op) {
        // 往左,說明洞往右
        if (ch == 'L') L--, R--;
        if (ch == 'R') L++, R++;
        if (ch == 'U') U--, D--;
        if (ch == 'D') U++, D++;
        add(U, L, D, R);
    }

    // 二分字首和
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            g[i][j] += g[i - 1][j] + g[i][j - 1] - g[i - 1][j - 1];
        }
    }

    // 統計答案
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (x - g[i][j] == k) cnt++;
        }
    }

    cout << cnt << endl;
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
}

M - 清空水箱

題意

逆時針給一個簡單圖形,沒有交叉和重合,但是有共線的,這個圖形是個水箱,裡面有水,要開若干個洞才能把水全排出去

疑惑

題解還是挺多的,但是我有個wa30的思路表示十分疑惑,希望有佬能幫我看看哪裡有問題

The 2022 ICPC Asia Nanjing Regional Contest IGDA,和令人疑惑的M

如圖,逆時針的話,前一條邊的極角是 \(a1\) ,後一條邊是 \(a2\),用 \(atan2\) 函式來算

只要滿足 \(a1>0\),\(a2>0\) ,\(a1>a2\)就說明需要開口

程式碼

#include <bits/stdc++.h>
using namespace std;
#define ld long double
#define endl '\n'
const double eps = 1e-7;
struct P {
    int x, y;
} a[2010];

void solve() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> a[i].x >> a[i].y;
    }
    a[n].x = a[0].x;
    a[n].y = a[0].y;
    a[n + 1].x = a[1].x;
    a[n + 1].y = a[1].y;
    n++;
    int ans = 0;
    for (int i = 1; i < n; i++) {
        long double a1 = atan2l(a[i - 1].y - a[i].y, a[i - 1].x - a[i].x);
        long double a2 = atan2l(a[i + 1].y - a[i].y, a[i + 1].x - a[i].x);
        if (a1 > eps && a2 > eps) {
            if (a2 <= a1) ans++;
        }
    }
    cout << ans << endl;
}
int t = 1;
int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    while (t--) {
        solve();
    }
    return 0;
}

相關文章