比賽鏈接
官方題解
Problem A. Kuroni and the Gifts
將 和 排序後輸出即可。
時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int a[MAXN], b[MAXN];
int main() {
int T; read(T);
while (T--) {
int n; read(n);
for (int i = 1; i <= n; i++)
read(a[i]);
for (int i = 1; i <= n; i++)
read(b[i]);
sort(a + 1, a + n + 1);
sort(b + 1, b + n + 1);
for (int i = 1; i <= n; i++)
printf("%d ", a[i]);
printf("\n");
for (int i = 1; i <= n; i++)
printf("%d ", b[i]);
printf("\n");
}
return 0;
}
Problem B. Kuroni and Simple Strings
最終剩餘的串一定可以找到一個分界點,使得其前面都是 ,後面都是 。
因此,若當前字符串以 開頭,或以 結尾,顯然可以刪除之。
否則,字符串的開頭和結尾將構成一對匹配的括號,需要對其進行操作。
由此,我們可以看到,若需要進行操作,則至多需要進行一次操作。
時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
bool vis[MAXN];
char s[MAXN];
int main() {
scanf("%s", s + 1);
int n = strlen(s + 1);
int l = 1, r = n, k = 0;
while (l <= r) {
if (s[l] == ')') l++;
else if (s[r] == '(') r--;
else {
vis[l] = vis[r] = true;
k++, l++, r--;
}
}
if (k) {
printf("%d\n%d\n", 1, 2 * k);
for (int i = 1; i <= n; i++)
if (vis[i]) printf("%d ", i);
printf("\n");
} else printf("%d\n", 0);
return 0;
}
Problem C. Kuroni and Impossible Calculation
若 ,根據抽屜原理,必然存在兩個同餘的數,則答案爲 。
否則,有 ,可以暴力計算答案。
時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, P, a[MAXN];
int main() {
read(n), read(P);
if (n > P) puts("0");
else {
int ans = 1;
for (int i = 1; i <= n; i++)
read(a[i]);
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
ans = 1ll * ans * (a[j] - a[i]) % P;
cout << ans << endl;
}
return 0;
}
Problem D. Kuroni and the Celebration
通過一次詢問,我們可以得到樹上一條路徑上深度最小的點。
那麼,考慮詢問一對不同的葉子節點 。
若得到的回答是 中的一者,則可以直接確定根節點。
否則,令答案爲 ,我們可以確定根節點不在 所在的 的子樹中。因此,可以刪去這兩個子樹,在剩餘的樹上重複這一過程。一次詢問將導致候選點集的大小至少 。
時間複雜度 ,使用操作 次。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
bool res[MAXN]; int vis[MAXN], task;
int n; vector <int> a[MAXN];
void erase(vector <int> &a, int x) {
for (unsigned i = 0; i < a.size(); i++)
if (a[i] == x) {
swap(a[i], a[a.size() - 1]);
a.pop_back();
return;
}
}
int cntres() {
int cnt = 0;
for (int i = 1; i <= n; i++)
cnt += res[i];
return cnt;
}
void col(int pos, int fa) {
vis[pos] = task;
for (auto x : a[pos])
if (x != fa) col(x, pos);
}
void dres(int pos, int fa) {
res[pos] = false;
for (auto x : a[pos])
if (x != fa) dres(x, pos);
}
int main() {
read(n);
for (int i = 1; i <= n; i++)
res[i] = true;
for (int i = 1; i <= n - 1; i++) {
int x, y; read(x), read(y);
a[x].push_back(y);
a[y].push_back(x);
}
while (cntres() != 1) {
int x = 0, y = 0;
for (int i = 1; i <= n; i++)
if (res[i] && a[i].size() == 1) {
y = x;
x = i;
}
assert(x != 0 && y != 0);
cout << '?' << ' ' << x << ' ' << y << endl;
int z, rx = 0, ry = 0; read(z);
if (z == x || z == y) {
cout << '!' << ' ' << z << endl;
return 0;
}
for (auto p : a[z]) {
task++;
col(p, z);
if (vis[x] == task) rx = p;
if (vis[y] == task) ry = p;
}
erase(a[z], rx);
erase(a[z], ry);
dres(rx, z), dres(ry, z);
}
for (int i = 1; i <= n; i++)
if (res[i] == true) {
cout << '!' << ' ' << i << endl;
return 0;
}
return 0;
}
Problem E. Kuroni and the Score Distribution
考慮滿足 的三元組 。
對於固定的 ,至多能夠產生 個三元組。並且,若令 ,這個上界對每一個 均可取到。因此,令 ,當 ,問題無解。
若 ,則可以直接令 。
否則,一定存在最小的 ,使得 。
考慮按照如下方式構造:
可以保證 產生了 個三元組,並且剩餘 不能夠產生三元組。
時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, m, a[MAXN];
int main() {
read(n), read(m);
for (int i = 1; i <= n; i++)
a[i] = 5e8 + 2e4 * i;
for (int i = 1; i <= n; i++) {
if ((i - 1) / 2 <= m) {
a[i] = i;
m -= (i - 1) / 2;
} else {
a[i] = i - 1 + (i - 2 * m);
m = 0;
break;
}
}
if (m > 0) {
puts("-1");
return 0;
}
for (int i = 1; i <= n; i++)
printf("%d ", a[i]);
printf("\n");
return 0;
}
Problem F. Kuroni and the Punishment
若固定所有數最終的一個公約數 ,顯然可以通過 貪心求出最少步數。
考慮取 ,則可以發現,操作次數不超過 ,因此答案在 以內。
這表明,在最優方案中,有至少一半的數被操作的次數在 以內。
由此,可以考慮隨機一個數 ,令 爲 中所有出現過的質因子更新答案。
若隨機 次,可以保證正確率在 以上。
時間複雜度 ,其中 爲迭代次數。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n; ll a[MAXN];
int calc(ll g) {
ll ans = 0;
for (int i = 1; i <= n; i++) {
if (a[i] < g) ans += g - a[i];
else {
ll tmp = a[i] % g;
ans += min(tmp, g - tmp);
}
}
if (ans > n) return n;
else return ans;
}
int work(ll tmp) {
int ans = n;
if (tmp == 0) return n;
for (int i = 2; 1ll * i * i <= tmp; i++)
while (tmp % i == 0) {
tmp /= i;
chkmin(ans, calc(i));
}
if (tmp != 1) chkmin(ans, calc(tmp));
return ans;
}
int main() {
read(n);
srand('X' + 'Y' + 'X');
for (int i = 1; i <= n; i++)
read(a[i]);
random_shuffle(a + 1, a + n + 1);
int ans = n;
for (int i = 1; i <= 40; i++) {
int pos = ((rand() << 15) + rand()) % n + 1;
chkmin(ans, work(a[pos]));
chkmin(ans, work(a[pos] + 1));
chkmin(ans, work(a[pos] - 1));
}
cout << ans << endl;
return 0;
}
Problem G. Kuroni and Antihype
新增一個權值爲 的人,令自行加入的人是此人所邀請的。
拋開連邊方式,考慮如下子問題:
給定一張 個點的有向圖,其中一個點是源點,與所有點之間均有邊,需要選擇權值儘量大的 條邊,使得源點可以通過所選的邊到達所有點。
若不考慮圖的特殊性,這個問題必須轉化爲最小樹形圖來解決。
本題中,這張有向圖具有一定特殊性:
、若存在邊 ,則一定存在邊
、邊 的權值爲
注意到最終形成的樹形圖上,每個點的入度均爲 ,因此,可以將邊權更改爲 ,在最終答案中減去 。此時,問題被轉化爲了無向圖最小生成樹的問題。
那麼,用並查集維護連通性,通過枚舉子集從邊權大到小加入所有邊即可。
時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1 << 18;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
bool vis[MAXN]; ll ans;
int n, cnt[MAXN], f[MAXN];
int find(int x) {
if (f[x] == x) return x;
else return f[x] = find(f[x]);
}
void work(int x, int y) {
ll inc = x + y, cmt = 1;
x = find(x), y = find(y);
if (x == y) return;
if (!vis[x]) cmt += cnt[x] - 1;
if (!vis[y]) cmt += cnt[y] - 1;
ans += inc * cmt, f[x] = y;
vis[x] = vis[y] = true;
}
int main() {
read(n), cnt[0]++;
for (int i = 1; i <= n; i++) {
int x; read(x);
cnt[x]++, ans -= x;
}
int u = (1 << 18) - 1;
for (int i = 0; i <= u; i++)
f[i] = i, vis[i] = false;
for (int i = u; i >= 0; i--)
for (int j = i; j > (i ^ j); j = (j - 1) & i)
if (cnt[j] && cnt[i ^ j]) work(j, i ^ j);
cout << ans << endl;
return 0;
}
Problem H. Kuroni the Private Tutor
考慮固定學生的得分 ,判斷該得分分佈是否可能出現。
不計時間複雜度,我們可以用有上下界的網絡流進行判斷。
考慮構造一個左側 個點,右側 個點的完全二分圖,各邊流量限制爲 。源點連向左側第 個點,流量下界爲 ,上界爲 。右側第 個點連向匯點,流量限制爲 。那麼,該得分分佈可能出現,當且僅當存在流量大小爲 的可行流。
由於上述二分圖構造規律顯著,可以考慮用最大流最小割定理對存在可行流的條件加以分析。
令 均爲有序數組, 分別爲它們的前綴和。
則存在可行流當且僅當對於任意的 ,以下兩個條件均成立:
、
、
考慮從小到大枚舉 ,那麼,使得不等式左側取到最小值的 是單調不增的,可以方便地用雙指針在 的時間內進行判斷。
由上面的分析,我們也可以發現,我們希望前綴和數組 中的元素儘可能大。
根據給定條件,我們可以確定一個所有 的下界,同時,還會剩餘一些能夠分配的分數。此時,我們會希望將分數儘可能分配給靠前的 ,使得前綴和數組 中的元素儘可能大。
兩個答案均可以二分,二分後構造合適的 判斷是否合法即可。
時間複雜度
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, m, q, l[MAXN], r[MAXN], x[MAXN];
int a[MAXN]; ll t, sa[MAXN], sl[MAXN], sr[MAXN];
bool check() {
for (int i = 1; i <= m; i++)
sa[i] = sa[i - 1] + a[i];
for (int i = 0, j = m; i <= n; i++) {
while (j >= 1 && a[j] >= n - i) j--;
if (sl[i] + sa[j] + 1ll * (n - i) * (m - j) < sl[n]) return false;
if (sr[i] + sa[j] + 1ll * (n - i) * (m - j) < t) return false;
}
return true;
}
bool check(int cnt, int val) {
ll sum = 0; bool flg = true;
for (int i = 1; i <= m; i++) {
a[i] = max(a[i - 1], x[i]), sum += a[i];
if (m - i + 1 <= cnt) flg &= x[i] == -1;
}
if (!flg) {
if (a[m] < val) return false;
for (int i = 1; i <= cnt; i++) {
if (x[m - i + 1] != -1 && a[m - i + 1] != a[m]) return false;
sum += a[m] - a[m - i + 1];
a[m - i + 1] = a[m];
}
} else {
for (int i = 1; i <= cnt; i++) {
if (a[m - i + 1] < val) {
sum += val - a[m - i + 1];
a[m - i + 1] = val;
}
}
}
if (sum > t) return false;
sum = t - sum; ll bak = sum;
for (int i = 1, last = 0; i <= m; i++)
if (x[i] != -1 || m - i + 1 <= cnt) {
int rng = i - last - 1, inc = a[i] - a[last];
if (1ll * inc * rng <= sum) {
sum -= 1ll * inc * rng;
for (int j = last + 1; j <= i - 1; j++)
a[j] += inc;
} else {
inc = sum / rng, sum -= 1ll * inc * rng;
for (int j = last + 1; j <= i - 1; j++) {
a[j] += inc;
if (i - j <= sum) a[j]++;
} sum = 0;
break;
}
last = i;
}
if (!flg) {
if (sum != 0) return false;
} else {
if (sum != 0) {
int tmp = cnt;
while (tmp < m && x[m - tmp] == -1) tmp++;
ll inc = (sum - 1) / tmp + 1;
for (int i = 1; i <= m; i++) {
a[i] = max(a[i - 1], x[i]);
if (m - i + 1 <= cnt) chkmax(a[i], val);
}
if (a[m] + inc > n) return false;
for (int i = 1; i <= cnt; i++)
a[m - i + 1] += inc;
sum = bak - inc * cnt;
if (sum < 0) return false;
for (int i = 1, last = 0; i <= m; i++)
if (x[i] != -1 || m - i + 1 <= cnt) {
int rng = i - last - 1, inc = a[i] - a[last];
if (1ll * inc * rng <= sum) {
sum -= 1ll * inc * rng;
for (int j = last + 1; j <= i - 1; j++)
a[j] += inc;
} else {
inc = sum / rng, sum -= 1ll * inc * rng;
for (int j = last + 1; j <= i - 1; j++) {
a[j] += inc;
if (i - j <= sum) a[j]++;
} sum = 0;
break;
}
last = i;
}
if (sum != 0) return false;
}
}
return check();
}
int main() {
read(n), read(m);
for (int i = 1; i <= n; i++)
read(l[i]), read(r[i]);
sort(l + 1, l + n + 1);
sort(r + 1, r + n + 1);
for (int i = 1; i <= n; i++) {
sl[i] = sl[i - 1] + l[i];
sr[i] = sr[i - 1] + r[i];
}
for (int i = 1; i <= m; i++)
x[i] = -1;
read(q);
for (int i = 1; i <= q; i++) {
int pos; read(pos);
read(x[m - pos + 1]);
}
read(t);
if (!check(1, 0)) {
puts("-1 -1");
return 0;
}
int l = 1, r = m;
while (l < r) {
int mid = (l + r + 1) / 2;
if (check(mid, 0)) l = mid;
else r = mid - 1;
}
int cnt = l; l = 0, r = n;
cout << cnt << ' ';
while (l < r) {
int mid = (l + r + 1) / 2;
if (check(cnt, mid)) l = mid;
else r = mid - 1;
}
cout << l << endl;
return 0;
}