比赛链接
官方题解
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;
}