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