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