2023 ICPC 合肥区域赛题解 更新至 6 题(The 2023 ICPC Asia Hefei Regional Contest )

Preface

只能说阅读理解能力有待提高,\(B\)题看了半天愣是看不懂一点。只能跳了。

依旧是复习篇,感觉队友当时开出来的\(dp\)难度不低,感慨张神的强大。

我会在代码一些有必要的地方加上注释,签到题可能一般就不会写了.

以下是代码火车头:

#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(const vector<T> &tem) {
    for (const auto &x: tem) cout << x << ' ';
    cout << endl;
}

template<typename T>
void cc(const T &a) { cout << a << endl; }

template<typename T1, typename T2>
void cc(const T1 &a, const T2 &b) { cout << a << ' ' << b << endl; }

template<typename T1, typename T2, typename T3>
void cc(const T1 &a, const T2 &b, const T3 &c) { cout << a << ' ' << b << ' ' << c << endl; }

void cc(const string &s) { cout << s << endl; }

void fileRead() {
#ifdef LOCALL
    freopen("D:\\AADVISE\\Clioncode\\untitled2\\in.txt", "r", stdin);
    freopen("D:\\AADVISE\\Clioncode\\untitled2\\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 double max(double a, double b) {
    if (a < b) return b;
    return a;
}

inline int min(int a, int b) {
    if (a < b) return a;
    return b;
}

inline double min(double a, double 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; }
void cmin(double &a, const double &b) { if (b < a) a = b; }
void cmax(double &a, const double &b) { if (b > a) a = b; }
using PII = pair<int, int>;
using i128 = __int128;
using vec_int = std::vector<int>;
using vec_char = std::vector<char>;
using vec_double = std::vector<double>;
using vec_int2 = std::vector<std::vector<int> >;
using que_int = std::queue<int>;

Problem B. Queue Sorting

愣是看不懂一点呢,逃了逃了。。。

Problem C. Cyclic Substrings

评价是回文自动机板子题呢。

首先把字符串×2,然后就是查询那些回文字符串,条件是结尾的下标是大于\(n\)的,而且长度是小于等于\(n\),再对长度\(*\)次数\(*\)次数的求和。

那么我们首先就当下标大于\(n\)\(siz\)\(++\)。然后\(len\)是必须一直都要赋值的。所以我们需要在\(count\)函数里判断\(Len\)就好了。(此处\(siz\)(在\(count\)之后代表当前下标结尾的最长的回文字符串的个数,\(len\)是回文字符串的长度)(如果此处看不懂的话建议去学一下\(PAM\),学之后就懂了)

//--------------------------------------------------------------------------------
const int N = 6e6 + 10;
const int M = 1e6 + 10;
const int mod = 998244353;
const int INF = 1e16;
int n, m, T;
int ans;
//--------------------------------------------------------------------------------
//struct or namespace:
class PAM {
private:
    //TODO 如果不是小写字母就要修改
    constexpr static int M = 11;
    int fan(char x) { return (x - '0'); }

    void erase_son(int x) {
        rep(i, 0, M - 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;
        }
        if (id > n)
            F[x].siz++;
        las = x;
        return;
    }

public:
    string s;
    int s_len, las, tot;

    struct node {
        int len;
        int fail;
        int cnt;
        int son[M];
        int siz;
    };

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

    void add(char c) {
        s_len++;
        s.push_back(c);
        add(fan(c), s_len);
    }

    void count() {
        rep2(i, tot, 2) {
            F[F[i].fail].siz += F[i].siz;
            if (F[i].len <= n)
                ans += F[i].siz * F[i].siz % mod * F[i].len % mod;
            ans %= mod;
        }
    }

    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;
        s = "$";
        erase_son(0), erase_son(1);
    }
};

PAM pam;
//--------------------------------------------------------------------------------

signed main() {
    fileRead();
    kuaidu();
    T = 1;

    //cin >> T;


    while (T--) {
        cin >> n;
        string s;
        cin >> s;
        s = s + s;
        pam.clear();

        for (auto &x: s) {
            pam.add(x);
        }
        pam.count();
        cc(ans);
    }
    return 0;
}

Problem E. Matrix Distances

评价是有一些小ex的一道签到。

能够显然的知道\(x\)\(y\)分开计算就好了,然后是写一个函数,参数是一个数组。

首先将这个数组排序,然后我们考虑假设当前的位置是\(i\),设\(g_i\)是前缀和,\(f_i\)是后缀和。在\(i\)之前的是\(i*a_i-g_i\),在\(i\)之后的是\(f_i-(n-i+1)*a_i\),所以求出来前缀和和后缀和之后根据这个式子计算就好了。

//--------------------------------------------------------------------------------
const int N = 1e6 + 10;
const int M = 1e3 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
int A[M][M];
int f[N], g[N], B[N];
map<int,int> mp;
vec_int dp1[N], dp2[N];
//--------------------------------------------------------------------------------
//struct or namespace:

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

int dfs(vec_int &A) {
    sort(A.begin(), A.end());
    int len = A.size();
    rep(i, 1, len) B[i] = A[i - 1];
    f[len] = B[len], g[1] = B[1];
    rep(i, 2, len) g[i] = g[i - 1] + B[i];
    rep2(i, len-1, 1) f[i] = f[i + 1] + B[i];
    int ans = 0;
    rep(i, 1, len) ans += f[i] - g[i] - (len + 1) * B[i] + 2 * i * B[i];
    return ans;
}

signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        cin >> n >> m;
        int tot = 0;
        rep(i, 1, n)
            rep(j, 1, m) {
                cin >> A[i][j];
                if (mp[A[i][j]] == 0) mp[A[i][j]] = ++tot;
                int x = mp[A[i][j]];
                dp1[x].push_back(i);
                dp2[x].push_back(j);
            }
        int ans = 0;
        rep(i, 1, tot) {
            ans += dfs(dp1[i]);
            ans += dfs(dp2[i]);
        }
        cc(ans);
    }
    return 0;
}

Problem F. Colorful Balloons

签签签签签,就不多说了。直接根据题意模拟就好了。

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
map<string,int> mp;
//--------------------------------------------------------------------------------
//struct or namespace:

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

signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        cin >> n;
        rep(i, 1, n) {
            string s;
            cin >> s;
            mp[s]++;
        }
        int fl = 0;
        string ans;
        for (auto &[s,val]: mp) {
            if (n % 2) if (val >= (n + 1) / 2) ans = s, fl = 1;
            if (n % 2 == 0) if (val > n / 2) ans = s, fl = 1;
        }
        if (fl) cout << ans << endl;
        else cout << "uh-oh" << endl;
    }
    return 0;
}

Problem G. Streak Manipulation

首先二分就不说了,直接二分能不能凑出来\(k\)个长度大于等于\(mid\)\(1\),最后二分出来的答案就是最后的答案。

所以\(check\)函数就是判断是否有\(k\)个长度\(>=\)\(mid\)\(1\)。此处考虑\(dp\),我们设\(dp_{i,j}\)是代表前\(i\)项,有\(j\)个大于等于\(mid\)且第\(i\)项是有连续的大于等于\(k\)\(1\)的情况下所用的最少的更换的次数。

那么\(dp_{i,j}=\min{(dp_{1,j-1},dp_{2,j-1},...,)}+(pre_i-pre_{i-mid})\)。当然这个地方还需要一些细节,左边\(min\)的右边的边界要根据\(i-mid\)的位置是不是\(1\),如果是的话就接着一直找到最左边的\(0\)

然后关于这个\(min\),由于本人实在是懒+线段树选手,这种情况是直接上线段树,但是结果被卡\(log\)了,算的时间复杂度是\(3e8\)还有大常数,结果\(t\)了。改成前缀优化就好了。

//--------------------------------------------------------------------------------
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
int k;
int pre[N], dp[N][7], to[N];
//--------------------------------------------------------------------------------
//struct or namespace:
//dp2数组是用来记录dp数组的前缀最小值的
int dp2[N][7];
//--------------------------------------------------------------------------------

bool check(int len) {
    // seg[0].add(0, 0, 0);
    rep(i, 0, k)
        rep(j, 0, n) dp2[j][i] = INF;
    dp2[0][0] = 0;

    rep(j, 1, k) {
        rep(i, 1, n) {
            cmin(dp2[i][j - 1], dp2[i - 1][j - 1]);
        }

        rep(i, 1, n) {
            if (i - len < 0) {
                dp[i][j] = INF;
                dp2[i][j] = dp[i][j];
                continue;
            }

            if (to[i - len] - 1 < 0) dp[i][j] = INF;
            else dp[i][j] = dp2[to[i - len] - 1][j - 1] + (pre[i] - pre[i - len]);

            dp2[i][j] = dp[i][j];
        }
    }
    int ans = INF;
    rep(i, 1, n) cmin(ans, dp[i][k]);
    if (ans <= m) return 1;
    return 0;
}

signed main() {
    fileRead();
    kuaidu();
    T = 1;
    // cc(log2(200000) * log2(200000) * 200000 * 5);
    //cin >> T;
    while (T--) {
        cin >> n >> m >> k;
        string s;
        cin >> s;

        int len = s.size();
        to[0] = 1;
        if (s[0] == '0') to[1] = 1;
        else to[1] = 0;
        rep(i, 0, len-1) {
            if (s[i] == '0') pre[i + 1]++;
            pre[i + 1] += pre[i];
        }
//to函数用来找到当前i点最左边的0的位置
        rep(i, 2, n) {
            if (s[i - 1] == '1') to[i] = to[i - 1];
            else to[i] = i;
        }
        int l = -1, r = n + 1;
        while (l + 1 != r) {
            int mid = l + r >> 1;
            if (check(mid)) l = mid;
            else r = mid;
        }
        if (l == 0) l = -1;
        cc(l);
    }
    return 0;
}

Problem I. Linguistics Puzzle

这个题不是太难,感觉难度上应该要比\(b\)简单(虽然没有做\(b\)题,但是已经被\(b\)题的计数给吓到了)。

一个小阅读理解,给你一个乘法表,让你推出来每个字母代表的是什么数字。最后写下来实际是一个码农题。

直接暴力枚举就好了,因为实际打表下来发现每个数字用到的次数并不是太多,我们可以用二元组数组\(PII \ \ \ biao_i\),代表数字\(i\)在十位和个位上出现的次数是多少,所以同样也会有一个\(to\)数组是代表字符在十位和个位上出现的次数。然后写一个暴力\(dfs\),在相同的二元组里面就枚举字符\(i\)选择的数字是谁。

关于时间复杂度的计算不会,只是感觉得一个数字如果选择有\(2\)的话,时间复杂度大概就是\(2^{n/2}\)那里,实际肯定要更小,因为有\(0,1\)这种选择会有很多之类的。

下面直接贴上码农屎山代码了:

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
map<PII, vec_int> mp, mp2;
vec_int res;
//数字x在十位和个位上出现的次数是多少
PII biao[N];

// 字符i选择的数字是谁
int book[N];

//数字i有没有被选择
bool xuan[N];

//字符i的出现的次数
PII to[N];

//存下来字符串有哪些
vector<string> qs;
//--------------------------------------------------------------------------------
//struct or namespace:

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

void pre() {
    rep(i, 0, n-1)
        rep(j, 0, n-1) {
            int l = i * j;
            res.push_back(l);
            if (l < n) {
                biao[l].second++;
            }
            else {
                biao[l / n].first++;
                biao[l % n].second++;
            }
        }
    sort(res.begin(), res.end());
    rep(i, 0, n-1) {
        mp[biao[i]].push_back(i);
    }
}

int fan(char x) {
    if (x <= 'z' and x >= 'a') return x - 'a';
    return 26 + x - 'A';
}

char back(int x) {
    if (x <= 25) return char(x + 'a');
    return char(x - 26 + 'A');
}

bool check() {
    vec_int tem;
    for (auto &s: qs) {
        if (s.size() == 1) tem.push_back(book[fan(s[0])]);
        else {
            tem.push_back(book[fan(s[0])] * n + book[fan(s[1])]);
        }
    }
    sort(tem.begin(), tem.end());
    rep(i, 0, n*n-1) {
        if (tem[i] != res[i]) return 0;
    }
    return 1;
}

int dfs(int x) {
    if (x == n) {
        if (check()) return 1;
        return 0;
    }
    for (auto &l: mp[to[x]]) {
        if (xuan[l]) continue;
        xuan[l] = 1;
        book[x] = l;
        if (dfs(x + 1)) return 1;
        xuan[l] = 0;
    }
    return 0;
}

signed main() {
    fileRead();

    kuaidu();
    T = 1;
    cin >> T;
    while (T--) {
        cin >> n;

        mp.clear();
        mp2.clear();
        res.clear();

        rep(i, 0, n) {
            to[i] = {0, 0};
            xuan[i] = 0;
            biao[i] = {0, 0};
        }
        qs.clear();

        pre();
        rep(i, 1, n*n) {
            string s;
            cin >> s;
            qs.push_back(s);
            if (s.size() == 1) {
                to[fan(s[0])].second++;
            }
            else {
                to[fan(s[0])].first++;
                to[fan(s[1])].second++;
            }
        }
        dfs(0);
        rep(i, 0, n-1) {
            rep(j, 0, n-1) {
                if (book[j] == i) {
                    cout << back(j);
                    break;
                }
            }
        }
        cout << endl;
    }
    return 0;
}

Problem J. Takeout Delivering

赛时写的解法是先跑出来\(1,n\)到每个点的路径的最小的最大值(用\(dis_1,dis_2\)表示),然后枚举边\([x,y,val]\),当\(val>=dis_1 \ \ and \ \ \ val>=dis_2\)的时候取三者里面的两个最小值就好了。但感觉这个题的难度很不好说,其实如果想不到的话难度会稍微高一点。

貌似有人是用瓶颈树求的最小的最大值,好久没有写了,再换个做法写一下子吧。

正常的解法:

//--------------------------------------------------------------------------------
const int N = 3e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e18;
int n, m, T;
vector<PII> A[N];
bool vis[N];
int dis1[N], dis2[N];
//--------------------------------------------------------------------------------
//struct or namespace:
struct node {
    int x;
    int y;
    int val;
};

vector<node> ed;
//--------------------------------------------------------------------------------
void dfs(int st,int dis[N]) {
    rep(i, 1, n) dis[i] = INF, vis[i] = 0;
    dis[st] = 0;
    priority_queue<PII> F;
    F.push({0, st});
    while (!F.empty()) {
        auto [_,x] = F.top();
        F.pop();
        if (vis[x]) continue;
        vis[x] = 1;
        for (auto &[y,val]: A[x]) {
            if (dis[y] > max(val, dis[x])) {
                dis[y] = max(val, dis[x]);
                F.push({-val, y});
            }
        }
    }
}

signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        cin >> n >> m;
        rep(i, 1, m) {
            int a, b, c;
            cin >> a >> b >> c;
            A[a].push_back({b, c});
            A[b].push_back({a, c});
            ed.push_back({a, b, c});
            ed.push_back({b, a, c});
        }
        dfs(1, dis1);
        dfs(n, dis2);
        int ans = INF;

        for (auto &[x,y,val]: ed) {
            vec_int tem;
            if (!(val >= dis1[x] and val >= dis2[y])) continue;
            tem.push_back(val);
            tem.push_back(dis1[x]);
            tem.push_back(dis2[y]);

            sort(tem.begin(), tem.end());
            cmin(ans, tem[1] + tem[2]);
        }
        cc(ans);
    }
    return 0;
}

生成树解法:

(被这个办法卡空间了,感觉有点小阴间)

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

//--------------------------------------------------------------------------------
//struct or namespace:
struct ED {
	int x;
	int y;
	int val;
};

vector<ED> ed;
int D[N];
int tot;

namespace z {
	vector<int> A[N];
	int son[N], dep[N], fa[N];
	//重儿子,顶根,时间戳,子树最右端时间戳,时间戳会对应节点x
	int hea[N], up[N], dnf[N];
	int tot;

	void dfs(int x, int pa) {
		son[x] = 1;
		dep[x] = dep[pa] + 1;
		fa[x] = pa;
		int t = 0;
		for (auto y: A[x]) {
			if (y == pa) continue;
			dfs(y, x);
			son[x] += son[y];
			if (!t or son[t] < son[y]) t = y;
		}
		hea[x] = t;
	}

	void dfs2(int x, int pa, int ding) {
		dnf[x] = ++tot;
		up[x] = ding;
		if (hea[x]) dfs2(hea[x], x, ding);
		for (auto y: A[x]) {
			if (y == pa || y == hea[x]) continue;
			dfs2(y, x, y);
		}
	}

	void clear(int n) {
		tot = 0;
		rep(i, 1, n) {
			A[i].clear();
			hea[i] = up[i] = dnf[i] = 0;
		}
	}

	void add(int x, int y, int c = 1) {
		A[x].push_back(y);
	}

	int lca(int x, int y) {
		while (up[x] != up[y]) {
			if (dep[up[x]] < dep[up[y]]) swap(x, y);
			x = fa[up[x]];
		}
		if (dep[x] > dep[y]) swap(x, y);
		return x;
	}


	void work(int rt = 1) {
		dfs(rt, 0);
		dfs2(rt, 0, rt);
	}

};

class DSU {
	struct Info {
		int fa;
	};

	Info dsu[N];

public:
	Info &operator[](const int &x) { return dsu[find(x)]; }

	void clear(int n) {
		rep(i, 1, n) {
			//TODO 初始化
			dsu[i].fa = i;
		}
	}

	void merge(int x, int y, int val) {

		x = find(x), y = find(y);
		if (x == y) return;
		//TODO 合并操作
		tot++;
		dsu[x].fa = dsu[y].fa = tot;
		z::add(tot, x);
		z::add(tot, y);
		D[tot] = val;
	}

	int find(int x) {
		if (x == dsu[x].fa) return x;
		return dsu[x].fa = find(dsu[x].fa);
	}

	bool same(int x, int y) {
		x = find(x), y = find(y);
		return (x == y);
	}
};

DSU dsu;
int dis1[N], dis2[N];

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

signed main() {
	fileRead();
	kuaidu();
	T = 1;
	//cin >> T;
	while (T--) {
		cin >> n >> m;
		tot = n;
		dsu.clear(n + n);
		z::clear(n + n);
		rep(i, 1, m) {
			int a, b, c;
			cin >> a >> b >> c;
			ed.push_back({a, b, c});
		}

		sort(ed.begin(), ed.end(), [&](ED &q1, ED &q2) {
			return q1.val < q2.val;
		});
		for (auto &[x,y,val]: ed) {
			if (dsu.same(x, y)) continue;
			dsu.merge(x, y, val);

		}

		z::work(tot);
		rep(i, 1, n) {
			int t = z::lca(1, i);
			dis1[i] = D[t];
			int t2 = z::lca(n, i);
			dis2[i] = D[t2];
		}

		int ans = INF;

		for (auto &[x,y,val]: ed) {
			vec_int tem;
			if (!(val >= dis1[x] and val >= dis2[y])) continue;
			tem.push_back(val);
			tem.push_back(dis1[x]);
			tem.push_back(dis2[y]);

			sort(tem.begin(), tem.end());
			cmin(ans, tem[1] + tem[2]);

			tem.clear();
			if (!(val >= dis2[x] and val >= dis1[y])) continue;
			tem.push_back(val);
			tem.push_back(dis2[x]);
			tem.push_back(dis1[y]);

			sort(tem.begin(), tem.end());
			cmin(ans, tem[1] + tem[2]);

		}
		cc(ans);

	}
	return 0;
}

PostScript

真的好颓废呢,\(EC-Final\)打不了了,只能说现在纯纯没什么事干。

posted @ 2024-12-11 22:29  AdviseDY  阅读(23)  评论(0编辑  收藏  举报