2018 ICPC南京區域賽題解 更新至 8 題

AdviseDY發表於2024-11-24

2018 ICPC南京區域賽題解 更新至 8 題

The 2018 ACM-ICPC Asia Nanjing Regional Programming Contest

目錄
  • 2018 ICPC南京區域賽題解 更新至 8 題
  • The 2018 ACM-ICPC Asia Nanjing Regional Programming Contest
    • Preface
    • Problem A. Adrien and Austin
    • Problem D. Country Meow
    • Problem E. Eva and Euro coins
    • Problem G. Pyramid
    • Problem I. Magic Potion
    • Problem J. Prime Game
    • Problem K. Kangaroo Puzzle
    • Problem M. Mediocre String Problem
    • PostScript

Preface

隊友向要考試,這場和隊友張兩個人vp的,前期打得崩了,一直都是隊友向在我旁邊看我程式碼檢查,導致前期開弱智列舉質因數題debug半天,好在後面打得還好,挽回了回來。

我會在程式碼一些有必要的地方加上註釋,簽到題可能一般就不會寫了.

以下是程式碼火車頭:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <set>
#include <queue>
#include <map>
#include <unordered_map>
#include <iomanip>
#define endl '\n'
#define int long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define rep2(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
template<typename T>
void cc(vector<T> tem) { for (auto x : tem) cout << x << ' '; cout << endl; }
void cc(int a) { cout << a << endl; }
void cc(int a, int b) { cout << a << ' ' << b << endl; }
void cc(int a, int b, int c) { cout << a << ' ' << b << ' ' << c << endl; }
void fileRead() { 
#ifdef LOCALL
    freopen("D:\\AADVISE\\cppvscode\\CODE\\in.txt", "r", stdin); 
    freopen("D:\\AADVISE\\cppvscode\\CODE\\out.txt", "w", stdout); 
#endif
}
void kuaidu() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); }
inline int max(int a, int b) { if (a < b) return b; return a; }
inline int min(int a, int b) { if (a < b) return a; return b; }
void cmax(int& a, const int b) { if (b > a) a = b; }
void cmin(int& a, const int b) { if (b < a) a = b; }
using PII = pair<int, int>;
using i128 = __int128;

Problem A. Adrien and Austin

感到 \(k\) 不是 \(1\) 的時候應該就是先手贏,所以\(k\)\(1\)的時候特判這種情況就好了,具體證明的話就是拿中間的\(1\)個或者\(2\)個。需要注意的是當\(n\)\(0\)的情況。

此處強調,題意是指必須要拿連續的並且都有才行,中間沒有的就不可以拿。(題意沒看懂導致搞了好久假做法)

程式碼是隊友張寫的:

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

const int N = 200005;

void solve() {
	int n, k; cin >> n >> k;
	if ((k == 1 && n % 2 == 0) || (n == 0)) {
		cout << "Austin" << endl;
	} else {
		cout << "Adrien" << endl;
	}
	return;
}

signed main() {
	ios::sync_with_stdio(false), cin.tie(nullptr);
	//int T; cin >> T; while (T--)
		solve();
	return 0;
}

Problem D. Country Meow

首先,直接設每一維的中間的點是不可以的。具體\(hack\)可以想象一下二維平面裡的一個三角形,最小距離是其重心而不是那個所謂的中間的點。

隊友張一直在想如果暴力的求解,有一個\(O(n^4)\)的做法,就是去列舉三個點是圓,然後去找最遠的那個點,再掃一下是不是所有的點都在這個球裡面,(後來發現好像這個做法有點假,不一定是最遠的點,又想改成找最遠的那幾個點),但是實現起來有些太麻煩所以作罷。

然後我思考的時候想象成二維怎麼做,然後套用到三維上。如果固定\(x\)軸,那麼\(y\)軸上會形成一個單峰函式。如果\(x\)軸動了起來,那麼每次最後找到的那個數所形成的函式應該也還是會單峰的,所以考慮三分套三分的做法。到三維上就是套三個三分去做就好了。


//--------------------------------------------------------------------------------
//struct or namespace:
struct Point {
    double x;
    double y;
    double z;
};
vector<Point> A;
//--------------------------------------------------------------------------------

double dis(const Point& q1, const Point& q2) {
    double l1 = (q1.x - q2.x) * (q1.x - q2.x);
    double l2 = (q1.y - q2.y) * (q1.y - q2.y);
    double l3 = (q1.z - q2.z) * (q1.z - q2.z);
    return (l1 + l2 + l3);
}
double dis(Point& tem) {
    double ans = 0;
    for (auto x : A) {
        ans = max(ans, dis(x, tem));
    }
    // if (ans == 0) cc(ans);
    return ans;
}

double dfs(int dep, Point tem) {
    if (dep == 4) {
        return dis(tem);
    }
    double l = -100003, r = 100003;
    double ans = INF;
    while (r - l > eps) {
        double l1 = l + (r - l) / 3;
        double r1 = r - (r - l) / 3;
        Point tem1 = tem, tem2 = tem;
//每一層的三分分別改變x,y,z的值,每一次的三分都是返回其最遠的最小值。
        if (dep == 1) tem1.x = l1, tem2.x = r1;
        if (dep == 2) tem1.y = l1, tem2.y = r1;
        if (dep == 3) tem1.z = l1, tem2.z = r1;
        double ansl = dfs(dep + 1, tem1), ansr = dfs(dep + 1, tem2);
//ans取小值
        ans = min(ansl, ansr);
        if (ansl > ansr)  l = l1;
        else  r = r1;
    }
    return ans;
}

signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        cin >> n;
        rep(i, 1, n) {
            double a, b, c; cin >> a >> b >> c;
            A.push_back({ a,b,c });
        }
        Point tem = { 0,0,0 };
        double ans = dfs(1, tem);
        cout << fixed << setprecision(6) << sqrt(ans) << endl;
    }
    return 0;
}
/*


*/

Problem E. Eva and Euro coins

純純榜歪了,v的時候被\(M\)防住了,做不出來後直接放棄了,也沒有看別的題了。

感覺這個題確實不難,直接將一樣的消掉就好了。用棧去模擬這個操作,最後得到的只要比較一下一不一樣就好了。

要注意的是題意是隻有\(k\)內都一樣才可以進行操作,題意一開始讀錯了。。。

還有貌似\(1e6\)那邊的資料不能開\(vector\),用陣列才行,不然算作超時。下面的\(M\)也是要這樣。


//--------------------------------------------------------------------------------
const int N = 1e6 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
int k;
//--------------------------------------------------------------------------------
//struct or namespace:

//--------------------------------------------------------------------------------
int pre[N], st[N];
string dfs(string& s1) {
    int top = -1;
    int las = 0;
    rep(i, 0, s1.length() - 1) {
        st[++top] = i;
        if (top == 0 or top >= 1 and s1[st[top]] == s1[st[top - 1]]) las++;
        else las = 1;
        pre[i] = las;
        if (las >= k) {
            top -= k;
            if (top >= 0)
                las = pre[st[top]];
            else las = 0;
        }
    }

    string tem = "";
    // for (auto& x : st) tem = tem + s1[x];
    rep(i, 0, top) tem.push_back(s1[st[i]]);
    return tem;
}

signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        // cin >> k;
        // string s; cin >> s;
        // cc(dfs(s));
        cin >> n >> k;
        string s1, s2;
        cin >> s1 >> s2;
        string q1 = dfs(s1), q2 = dfs(s2);
        if (q1 == q2) cout << "Yes" << endl;
        else cout << "No" << endl;
    }
    return 0;
}
/*


*/

Problem G. Pyramid

數學\(master\)張神出手。這個題我是沒有什麼太大的思路,他說我們每一個點去找斜著的那些三角形,設當前的這個點是第一個點,向左一下,再向上一下的那個點為第二個點,同理,第二個點的也可以取向左一下,向上二下,一直這樣到邊界,。。。,向左兩下,向上一下,一直這樣下去。

所以當前這個點(假設是第\(x\)層從左往右的第\(i\)個三角形的右下角的點)的貢獻會是\(1+2+...+n=(n+1)*n/2\),所以那麼當前這一層的貢獻就也可以求出來了。會是一個\(n^3,n^2\)的式子。然後對所有的層也求和。

\(1^3+2^3+...+n^3\)的式子也可以找出來規律得出來一個通項式子,最後整理出來之後餵給張神就AC了。

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

const int N = 200005;
const int mod = 1e9 + 7;

int po(int x, int k) {
	int ans = 1;
	int t = x;
	while (k) {
		if (k & 1ll) {
			ans = ans * t % mod;
		}
		t = t * t % mod;
		k = k >> 1ll;
	}
	return ans;
}

void solve() {
	int n; cin >> n;
	int ans;
	ans = n * n % mod * (n + 1) % mod * (n + 1) % mod;
	ans += 2 * n * (n + 1) % mod * (2 * n + 1) % mod;
	ans %= mod;
	ans += 4 * n % mod * (n + 1) % mod;
	ans %= mod;

	ans = ans * po(24, mod - 2) % mod;
	cout << ans << endl;

	return;
}

signed main() {
	ios::sync_with_stdio(false), cin.tie(nullptr);
	int T; cin >> T; while (T--)
		solve();
	return 0;
}

Problem I. Magic Potion

一眼網路流,只能說這個題非常板子,源點向兩個點建邊,邊權依次是\(n,k\),這兩個點都向\(hero\)建一個邊權是\(1\)的邊,然後向自己對應的怪獸建邊,怪獸再向匯點建邊就好了。


//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;

//--------------------------------------------------------------------------------
//struct or namespace:這裡是網路流板子,直接ctrlc了
int st, ed;
struct DINIC {
    struct Z {
        int Next;
        int to;
        int val;
    };
    int cnt, n, m;
    vector<int> head, head1, dep;
    vector<Z> D;
    void init(int n1, int m1) {
        st = 0, ed = n1 + 5;
        n = n1 + 10, m = m1 + 10;
        cnt = 0;
        head.assign(n, -1);
        head1.resize(n);
        dep.resize(n);
        D.assign(m << 1, { 0,0,0 });
    }
    void add(int x, int y, int c) {
        D[cnt] = { head[x],y,c }; head[x] = cnt++;
        D[cnt] = { head[y],x,0 }; head[y] = cnt++;
    }
    bool bfs() {
        queue<int> F;
        dep.assign(n, 0);
        dep[st] = 1;
        head1[st] = head[st];
        F.push(st);
        while (!F.empty()) {
            int x = F.front(); F.pop();
            for (int i = head[x]; i != -1; i = D[i].Next) {
                auto [qwe, y, val] = D[i];
                if (dep[y] || val <= 0) continue;
                dep[y] = dep[x] + 1;
                head1[y] = head[y];
                F.push(y);
                if (y == ed) return 1;
            }
        }
        return 0;
    }
    int dinic(int x, int cost) {
        if (x == ed) return cost;
        int flow = 0;
        for (int i = head1[x]; i != -1; i = D[i].Next) {
            auto [qwe, y, val] = D[i];
            head1[x] = i;
            if (dep[y] != dep[x] + 1 || val <= 0) continue;
            int tem = dinic(y, min(cost - flow, val));
            if (!tem) dep[y] = -1;
            D[i].val -= tem; D[i ^ 1].val += tem;
            flow += tem;
            if (flow >= cost) break;
        }
        return flow;
    }
    int work() {
        int ans = 0;
        int flow;
        while (bfs()) while (flow = dinic(st, INF)) ans += flow;
        return ans;
    }
}liu;
//--------------------------------------------------------------------------------

signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        int k; cin >> n >> m >> k;
        liu.init(n + m + 4, n + n + n * m + 2 + m);
        //英雄編號1~n,怪獸編號n+1~n+m,兩個點的編號是n+m+1和n+m+2,st是源點,ed是匯點
        rep(i, 1, n) {
            int a; cin >> a;
            rep(j, 1, a) {
                int b; cin >> b;
                liu.add(i, n + b, 1);
            }
        }
        liu.add(st, n + m + 1, n);
        liu.add(st, n + m + 2, k);
        rep(i, 1, n) {
            liu.add(n + m + 1, i, 1);
            liu.add(n + m + 2, i, 1);
        }
        rep(i, 1, m) {
            liu.add(n + i, ed, 1);
        }
        cout << liu.work() << endl;
    }
    return 0;
}
/*


*/

Problem J. Prime Game

我寫的,純純傻逼題了,調了半天發現\(lasval\)忘記賦值了,調了得有半個小時才調出來。

但感覺這個題要比後面的題稍微難一些,感覺榜有一點點的歪。

考慮計算每一個質因數的貢獻。

假如陣列中質因數有\(2\)的是\([x,x,x,x,2,x,x,x,2,x,x,2,x]\),我們令區間內第一個不同的質因數是對其有貢獻的。那麼計算其貢獻值就是當前的下標,減去上一個的下標,這個差再乘上\(n+1\)減去當前下標的差。

我們先處理\(\sqrt 1000000\)以內的質數並且列舉,每一個數字含有當前的質因數\(x\),那麼就記錄其下標,然後將這個數字一直除\(x\),直到不能除為止。

最後剩餘的這個陣列都是大於\(\sqrt 100000\)的數,並且一定會是質數。(因為如果有含有兩個大於根號\(1000000\)的質數,那麼相乘就會大於\(1000000\),則不在資料範圍內)。

然後\(sort\)排個序,再計算下來就好了。

//--------------------------------------------------------------------------------
const int N = 1e6 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
int A[N];
bool biao[N], book[N];
int prime[N];
int tail;
//--------------------------------------------------------------------------------
//struct or namespace:
struct node {
    int id;
    int val;
};

//--------------------------------------------------------------------------------

void pre() {
//處理出來質數
    for (int i = 2; i < N; i++) {
        if (!biao[i]) {
            prime[++tail] = i;
            for (int j = i; j < N; j += i) biao[j] = 1;
        }
    }
    for (int i = 1; i <= tail; i++) {
        if (prime[i] > 1000) {
            tail = i - 1;
            break;
        }
    }
}
//計算貢獻值,把擁有一樣質因數的放進一個vector裡面
int dfs(vector<int>& tem) {
    tem.push_back(n + 1);
    int len = tem.size();
    int ans = 0;
    int las = 0;
    rep(i, 0, len - 1 - 1) {
        ans += (tem[i] - las) * (n + 1 - tem[i]);
        las = tem[i];
    }

    tem.clear();
    return ans;
}

signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;746773
    pre();
    // cc(prime[60001]);
    // cc(tail);
    while (T--) {
        cin >> n;
        rep(i, 1, n) {
            cin >> A[i];
        }

        int ans = 0;
//列舉質因數
        for (int i = 1; i <= tail; i++) {
            int x = prime[i];

            vector<int> tem;
            tem.clear();
            rep(j, 1, n) {
                if (book[j]) continue;

                if (A[j] % x == 0) {
                    tem.push_back(j);

                    while (A[j] % x == 0) {
                        A[j] /= x;
                    }
                }

                if (A[j] == 1) book[j] = 1;
            }

            if (tem.size()) ans += dfs(tem);

        }

        vector<node> g;
        g.clear();

        rep(i, 1, n) {
            if (book[i]) continue;

            g.push_back({ i, A[i] });
        }

        if (g.size() == 0) {
            cc(ans);
            continue;
        }
        // cc(ans);
//剩餘的排個序再計算
        sort(g.begin(), g.end(), [&](node q1, node q2) {
            if (q1.val == q2.val) return q1.id < q2.id;
            return q1.val < q2.val;
            });

        vector<int> tem;
        int lasval = 0;
        lasval = g[0].val;
        g.push_back({ 0, 0 });

        // cc(lasval);
        for (auto [id, val] : g) {

            if (val != lasval) {
                ans += dfs(tem);
                tem.clear();
                tem.push_back(id);
                lasval = val;//tmd就是這個地方,沒賦值!!!
                continue;
            }

            tem.push_back(id);
        }

        cc(ans);
    }

    return 0;
}

Problem K. Kangaroo Puzzle

最搞笑的一集,看了看資料範圍很小。感覺可以直接隨機化呢,交了一發直接A了。

最後的正解貌似是一個袋鼠走到另一個袋鼠,這樣子用搜尋去做合併的操作然後算下來不會超過\(5e4\)。懶得管了,過了就好。

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
char A[5] = { 'U','D','L','R' };
//--------------------------------------------------------------------------------
//struct or namespace:

//--------------------------------------------------------------------------------

signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        // cout << sqrt(3) * 0.5 << endl;
        srand(time(0));
        cin >> n >> m;
        rep(i, 1, n) {
            string s; cin >> s;
        }
        rep(i, 1, 20) cout << 'L';
        rep(i, 1, 20) cout << 'U';
        rep(i, 1, 20) cout << 'R';
        rep(i, 1, 20) cout << 'D';

        rep(i, 1, 50000 - 80) {
            int a = rand() % 4;
            cout << A[a];
        }


    }
    return 0;
}
/*


*/

Problem M. Mediocre String Problem

正解應該是用\(Z\)函式寫的。時間複雜度可以少一個\(logn\),但是不會寫\(Z\)函式,用的暴力寫法\(Hash+二分\)過的。十分運氣的\(984\)\(ms\)

首先,我們應該想到:應該是選擇字串\(s\)裡的一段迴文子串,然後左邊找和\(t\)字串前\(k\)的字首相反的字串。

換人話說就是,要找三個字串\(a,b,c\),分別代表字串\(s\)裡的一段子串,與\(a\)後面連續的字串\(s\)裡的一段迴文子串,\(t\)字串裡前\(k\)個字元組成的子串

其中\(a\)\(c\)要拼在一起也是迴文的。

我們列舉\(i\),找字串\(s\)裡第\(i\)個字元往前能夠與\(c\)相匹配上的最長的長度\(l\)。那麼\(1\)\(l\)中間我們都可以充當\(a\)

然後找到第\(i+1\)為開頭的迴文子串的數量\(p\),那麼\(l*p\)就是答案了。

關於這樣直接相乘答案是否會有重複,可以簡略思考下:

\(i\)++之後再進行操作,當我們固定\(tuple(i,j,k)\)裡的\(j\)不動,如果我們左端點此刻也和上一次的選擇一樣,那麼\(k\)就會比上一次的多\(1\),因為\(i++\)了,選擇的左端點到\(i\)之間的距離就是字串\(a\),同時也是\(c\)的長度。也就是\(k\)的大小,所以不會重複。

做法方面,求一個點為起點的迴文字串長度,我只會大力的迴文自動機搞過去。只需要把\(s\)字串弄反就好了。

然後判斷\(a\)\(c\)是迴文只需要把一個字串弄反,然後\(Hash\)就好了。所以直接把\(s\)字串\(reverse\)然後搞就好了。詳細請看程式碼。

(這樣做會\(T\),所以會Z函式的不用\(Hash\)改成\(Z\)函式就好了)

----------------------------------------------------------
const int N = 1e6 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;

//--------------------------------------------------------------------------------
//struct or namespace:

struct node {
    int len;
    int fail;
    int cnt;
    int son[30];
    int siz;
};
node F[N];

class PAM {
private:
    //TODO 如果不是小寫字母就要修改
    int fan(char x) { return (x - 'a' + 1); }
    void erase_son(int x) { rep(i, 0, 30 - 1) F[x].son[i] = 0; }
    int getfail(int x, int i) {
        while (s[i - F[x].len - 1] != s[i])	x = F[x].fail;
        return x;
    }
    void add(int c, int id) {
        int pa = getfail(las, id);
        int& x = F[pa].son[c];
        if (!x) {
            x = ++tot;
            erase_son(x);
            F[x].fail = F[getfail(F[pa].fail, id)].son[c]; if (F[x].fail == x) F[x].fail = 0;
            F[x].len = F[pa].len + 2;
            F[x].cnt = F[F[x].fail].cnt + 1;
            F[x].siz = 0;
        }
        F[x].siz++;
        las = x;
        return;
    }
    void resize(int n) { s.resize(n + 10); }
    void clear() {
        F[0].fail = 1; F[0].len = 0;
        F[1].fail = 0; F[1].len = -1;
        F[0].cnt = F[1].cnt = F[0].siz = F[1].siz = 0;
        tot = 1, las = 1; s_len = 0;
        erase_son(0), erase_son(1);
    }
public:
    string s;
    int s_len, las, tot;


    node& operator [](int x) { return F[x]; }

    PAM(int n) { resize(n); clear(); }
    void add(char c) { s[++s_len] = c; add(fan(c), s_len); }
    void count() { rep2(i, tot, 2) F[F[i].fail].siz += F[i].siz; }
};
PAM pam(N);

struct HASH {
    int base = 131;
    int P[2][N], pre[2][N];
    char S[N];
    int Mod[2];
    void init(const string& s) {//需要保證字串是從0開始的
        Mod[0] = 998244353;
        Mod[1] = 1e9 + 7;
        int n = s.size();
        for (int i = 1; i <= n; i++) S[i] = s[i - 1];
        P[0][0] = P[1][0] = 1;
        // P[0][0] = 1;
        for (int i = 0; i <= 1; i++) {
            for (int j = 1; j <= n; j++) {
                pre[i][j] = (pre[i][j - 1] * base % Mod[i] + S[j]) % Mod[i];
                P[i][j] = P[i][j - 1] * base % Mod[i];
            }
        }
    }
    PII qry(int l, int r) {
        int a, b; l++, r++;
        a = (pre[0][r] - pre[0][l - 1] * P[0][r - l + 1] % Mod[0] + Mod[0]) % Mod[0];
        b = (pre[1][r] - pre[1][l - 1] * P[1][r - l + 1] % Mod[1] + Mod[1]) % Mod[1];
        return { a,a };
    }
}h1, h2;
int pre[N];
int suf[N];
//--------------------------------------------------------------------------------

bool check(int l, int r) {
    PII q1, q2;
    q1 = h1.qry(l, r), q2 = h2.qry(0, r - l + 1 - 1);
    if (q1.first == q2.first and q1.second == q2.second) return 1;
    return 0;
}

signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        string s1, s2;
        cin >> s1 >> s2;
        reverse(s1.begin(), s1.end());
        h1.init(s1);
        h2.init(s2);
        int len = s1.size();
        rep(i, 0, len - 1) {
            pam.add(s1[i]);
            suf[i] = pam[pam.las].cnt;
        }
        // reverse(s1.begin(), s1.end());
        int ans = 0;

        rep2(i, len - 1, 1) {
            int l = i - 1, r = len;
            while (l + 1 != r) {
                int mid = l + r >> 1;
                if (check(i, mid)) l = mid;
                else r = mid;
            }
            ans += (l - i + 1) * suf[i - 1];
            // cc(i, l - i + 1, suf[i - 1]);
            // cout << i << " " << i << " " << l << endl;
            // if (i == 2) cc(l);
        }
        cc(ans);
    }
    return 0;
}
/*


*/

PostScript

瞎打ing,沒啥想做的,該搞工程啥的了。

相關文章