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\)打不了了,只能说现在纯纯没什么事干。