2018ICPC 亚洲区域赛南京站 题解 更新至 8 题

2018 ICPC南京区域赛题解 更新至 8 题

The 2018 ACM-ICPC Asia Nanjing Regional Programming Contest

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,没啥想做的,该搞工程啥的了。

posted @ 2024-11-24 17:22  AdviseDY  阅读(28)  评论(0编辑  收藏  举报