【比赛链接】
【A】Commentary Boxes
- 【题意】把 n n n个东西平均分给 m m m个人,可以花费 a a a的代价使得 n n n增加1或者花费 b b b的代价使得 n n n减少1,求最小代价。
- 【题解】显然只会选择只增加或只减少使得 n n n变成恰好为 m m m的最接近 n n n的两个倍数,所以答案就是 m i n { a ∗ ( m − n % m ) , b ∗ ( n % m ) } min\{a*(m-n\%m),b*(n\%m)\} min{a∗(m−n%m),b∗(n%m)}。
- 时间复杂度 O ( 1 ) O(1) O(1)
- 【代码】
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
LL n, m, a, b;
template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
x = 0; int f = 1; char ch = getchar();
while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
x *= f;
}
int main(){
read(n), read(m), read(a), read(b);
if (n % m == 0) printf("0\n");
else {
LL tmp = n % m;
printf("%I64d\n", min(a * (m - tmp), b * tmp));
}
return 0;
}
【B】Micro-World
- 【题意】有一些数, i i i能吞噬 j j j的条件是 a j < a i ≤ a j + K a_j<a_i≤a_j+K aj<ai≤aj+K,求最少剩几个数。
- 【题解】这是一种DAG形式的吞噬关系,那么如果一个数存在其他数能吞噬它,它以及它能吞噬的点就一定能被吞噬。把所有数排个序,则对于每个数,能吞噬它的数是单调的。数组扫一遍,看一下每个数是否能被某个数吞噬,统计一下答案即可。
- 时间复杂度 O ( N ) O(N) O(N)
- 【代码】
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LL long long
#define MAXN 200010
using namespace std;
int n, k, a[MAXN], ans, pos;
template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
x = 0; int f = 1; char ch = getchar();
while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
x *= f;
}
int main(){
read(n), read(k);
for (int i = 1; i <= n; ++i)
read(a[i]);
sort(a + 1, a + 1 + n);
ans = n; pos = 1;
for (int i = 1; i <= n; ++i){
while (a[pos] + k < a[i]) ++pos;
while (a[pos] + k >= a[i] && a[i] > a[pos]) --ans, ++pos;
}
printf("%d\n", ans);
return 0;
}
【C】Bracket Sequences Concatenation Problem
- 【题意】有一些括号序列,问有多少种不同的连接方案使得两个序列首尾相连后可以得到一个匹配的括号序列。这里认为当 x ̸ = y x\not=y x̸=y时, < x , y > <x,y> <x,y>与 < y , x > <y,x> <y,x>是不同的, < x , x > <x,x> <x,x>也是一种合法的连接方案。
- 【题解】显然,每个括号序列里已经匹配的括号对答案没有影响,先用一个栈对读进来的原始括号序列预处理一下。那么最后只有形如"(((",")))",")))((("的括号序列,第一种和第二种我们用括号个数来表示这个串,用正负号来区分是左括号还是右括号,那么和为0的括号序列是可以匹配的。第三种无论如何都无法匹配,直接舍掉。开个数组统计一下每种括号序列有几个,最后把对应匹配的数量相乘统计入答案。
- 时间复杂度 O ( l e n ) O(len) O(len)
- 【代码】
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LL long long
#define MAXN 300100
using namespace std;
int n, a[MAXN * 2], sta[MAXN], top;
char s[MAXN];
LL ans;
template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
x = 0; int f = 1; char ch = getchar();
while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
x *= f;
}
int main(){
read(n);
for (int i = 1; i <= n; ++i){
scanf("%s", s + 1);
int len = strlen(s + 1);
top = 0;
for (int j = 1; j <= len; ++j){
if (s[j] == '(') sta[++top] = -1;
else{
if (sta[top] == -1) --top;
else sta[++top] = 1;
}
}
if (top == 0) {++a[MAXN]; continue;}
if (sta[top] != sta[1]) continue;
int tmp = 0;
for (int j = 1; j <= top; ++j)
tmp += sta[top];
++a[tmp + MAXN];
}
int maxn = 300000;
ans = 1ll * a[MAXN] * a[MAXN];
for (int i = 1; i <= maxn; ++i)
ans += 1ll * a[MAXN + i] * a[MAXN - i];
printf("%I64d\n", ans);
return 0;
}
【D】Graph And Its Complement
- 【题意】构造一张有 n n n个点的无向图,使得其中的连通分量的个数为 a a a,其补图的连通分量的个数为 b b b,或输出无解。
- 【题解】考虑一张不连通的图,它的补图一定是联通的,因为每个连通分量里的每一个点一定能与其他联通分量里的点直接相连。那么如果 a a a和 b b b全都非1的话,无解。不妨令 b = 1 b=1 b=1,那么我们只要构造一张连通分量的个数为 a a a的图即可。注意特判 n n n为2,3时的一些特殊情况。
- 时间复杂度 O ( N 2 ) O(N^2) O(N2)
- 【代码】
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
int n, a, b, tmp;
template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
x = 0; int f = 1; char ch = getchar();
while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
x *= f;
}
int main(){
read(n), read(a), read(b);
if (!(a == 1 || b == 1)) {printf("NO\n"); return 0;}
if (n == 2 && a == 1 && b == 1) {printf("NO\n"); return 0;}
if (n == 3 && a == 1 && b == 1) {printf("NO\n"); return 0;}
if (a == 1) {swap(a, b); tmp = 1;}
printf("YES\n");
if (a == 1 && b == 1){
for (int i = 1; i <= n; ++i){
for (int j = 1; j <= n; ++j){
if (i == j) printf("%d", 0);
else if (i + 1 == j || j + 1 == i) printf("%d", 1);
else printf("%d", 0);
}
printf("\n");
}
} else {
for (int i = 1; i <= n; ++i){
for (int j = 1; j <= n; ++j){
if (i == j) printf("%d", 0);
else if (i == 1 && j <= n - a + 1 && j != 1) printf("%d", 1 ^ tmp);
else if (j == 1 && i <= n - a + 1 && i != 1) printf("%d", 1 ^ tmp);
else printf("%d", 0 ^ tmp);
}
printf("\n");
}
}
return 0;
}
【E】Post Lamps
- 【题意】有一条 [ 0 , n − 1 ] [0,n-1] [0,n−1]的数轴,有 1 1 1~ k k k共 k k k种数量为正无穷的灯, k k k种灯每盏的价格为 a k a_k ak,放在 x x x处能照亮的范围是 [ x , x + k − 1 ] [x,x+k-1] [x,x+k−1]。数轴上有 m m m个位置是不能放灯的,问使得数轴全被照亮的最少花费或输出无解。
- 【题解】直接枚举用哪一种灯然后贪心地放,从一边开始扫,直到遇到一个必须放灯的位置再放。
- 时间复杂度 O ( N ln N ) O(N\ln{N}) O(NlnN)
- 【代码】
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LL long long
#define MAXN 1000010
using namespace std;
int n, m, k, a[MAXN], nxt[MAXN], w[MAXN];
LL ans;
template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
x = 0; int f = 1; char ch = getchar();
while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
x *= f;
}
int main(){
read(n), read(m), read(k);
for (int i = 1; i <= m; ++i){
int t; read(t);
a[t + 1] = 1;
}
for (int i = 1; i <= k; ++i)
read(w[i]);
if (a[1] == 1) {printf("-1\n"); return 0;}
nxt[n + 1] = n + 1;
for (int i = n; i >= 1; --i)
if (a[i]) nxt[i] = nxt[i + 1];
else nxt[i] = i;
int maxn = 1;
for (int i = 0; i <= n; ++i)
chkmax(maxn, nxt[i] - i + 1);
if (maxn > k) {printf("-1\n"); return 0;}
ans = 1000000000000ll;
for (int i = maxn; i <= k; ++i){
int last, cur = n, cnt = 0;
while (1){
++cnt;
last = cur - i + 1;
if (last <= 1) break;
last = nxt[last];
cur = last - 1;
}
chkmin(ans, 1ll * cnt * w[i]);
}
printf("%I64d\n", ans);
return 0;
}
【F】Flow Control
- 【题意】给出一张连通图以及每个点的出度与入度之差,求每条边的方向和边权,或输出无解。
- 【题解】考虑这张图的一棵生成树,若有一条非树边的边权非零,那么我们可以将这条边的边权变为零,并在这两点之间的树上路径上的每条边都加上这个值。因为每个点的出入度都可以用其他点平衡,所以无解的情况是所有点的度之和不为零,否则在生成树上树形DP即可。
- 时间复杂度 O ( N ) O(N) O(N)
- 【代码】
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LL long long
#define MAXN 200010
using namespace std;
int n, m, s[MAXN], ans[MAXN], fa[MAXN], size[MAXN];
struct info{int v, id;};
vector <info> a[MAXN];
struct edg{int u, v;}e[MAXN];
template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
x = 0; int f = 1; char ch = getchar();
while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
x *= f;
}
int find(int x){
if (fa[x] == x) return x;
else return (fa[x] = find(fa[x]));
}
void merge(int x, int y){
int fx = find(x), fy = find(y);
if (fx == fy) return;
if (size[fx] > size[fy]) swap(fx, fy);
size[fy] += size[fx], fa[fx] = fy;
}
int dfs(int pos, int dad, int id){
if (e[id].u == pos) ans[id] = -s[pos];
else ans[id] = s[pos];
for (unsigned i = 0, si = a[pos].size(); i < si; ++i){
int son = a[pos][i].v, sonid = a[pos][i].id;
if (son != dad){
if (e[id].u == pos) ans[id] += dfs(son, pos, sonid);
else ans[id] -= dfs(son, pos, sonid);
}
}
if (e[id].u == pos) return ans[id];
return -ans[id];
}
int main(){
read(n);
int tmp = 0;
for (int i = 1; i <= n; ++i)
read(s[i]), tmp += s[i], fa[i] = i, size[i] = 1;
if (tmp) {printf("Impossible\n"); return 0;}
read(m);
for (int i = 1; i <= m; ++i){
int u, v; read(u), read(v);
e[i].u = u, e[i].v = v;
if (find(u) != find(v)){
merge(u, v);
a[u].push_back((info){v, i});
a[v].push_back((info){u, i});
}
}
dfs(1, 0, 0);
printf("Possible\n");
for (int i = 1; i <= m; ++i)
printf("%d%c", ans[i], " \n"[i == m]);
return 0;
}
【G】GCD Counting
- 【题意】给出一棵树,每个节点有正整数点权,定义一条路径的权值为这条路径上所有点点权的gcd。求路径权值分别为 1 1 1~ 3 ∗ 1 0 5 3*10^5 3∗105的路径条数。
- 【题解】考虑倒着统计答案,计算权值为 i i i的答案时,统计一下路径权值为i的倍数的答案,然后把其他答案减去。我们把所有权值是 i i i的倍数的边的端点并起来,那么就会得到一些块,则路径权值为i的倍数的答案为 ∑ s i z e ∗ ( s i z e − 1 ) 2 \sum{\frac{size*(size-1)}{2}} ∑2size∗(size−1),因为本题中单点也算一条合法路径,要把单点一起统计进来,最后减掉其他i的倍数的答案即可。
- 时间复杂度 O ( N N ) O(N\sqrt{N}) O(NN)
- 【代码】
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LL long long
#define MAXN 200010
#define mp make_pair
using namespace std;
int n, w[MAXN], fa[MAXN], size[MAXN], maxn;
LL cur, ans[MAXN];
vector <int> a[MAXN];
vector <pair<int, int> > e[MAXN];
template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
x = 0; int f = 1; char ch = getchar();
while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
x *= f;
}
int gcd(int a, int b){
if (a == 0 || b == 0) return a + b;
return gcd(b, a % b);
}
int find(int x){
if (fa[x] == x) return x;
else return (fa[x] = find(fa[x]));
}
void merge(int x, int y){
int fx = find(x), fy = find(y);
if (fx == fy) return;
cur -= 1ll * size[fx] * (size[fx] - 1) / 2;
cur -= 1ll * size[fy] * (size[fy] - 1) / 2;
if (size[fx] > size[fy]) swap(fx, fy);
size[fy] += size[fx], fa[fx] = fy;
cur += 1ll * size[fy] * (size[fy] - 1) / 2;
}
int main(){
read(n);
for (int i = 1; i <= n; ++i)
fa[i] = i, size[i] = 1;
for (int i = 1; i <= n; ++i){
read(w[i]);
chkmax(maxn, w[i]);
a[w[i]].push_back(i);
}
for (int i = 1; i < n; ++i){
int u, v; read(u), read(v);
e[gcd(w[u], w[v])].push_back(mp(u, v));
}
for (int i = maxn; i >= 1; --i){
cur = 0;
for (int j = i; j <= maxn; j += i)
for (int k = 0, si = a[j].size(); k < si; ++k){
int to = a[j][k];
fa[to] = to, size[to] = 1;
++ans[i];
}
for (int j = i; j <= maxn; j += i)
for (int k = 0, si = e[j].size(); k < si; ++k){
pair <int, int> tmp = e[j][k];
merge(tmp.first, tmp.second);
}
ans[i] += cur;
for (int j = i + i; j <= maxn; j += i)
ans[i] -= ans[j];
}
for (int i = 1; i <= maxn; ++i)
if (ans[i]) printf("%d %I64d\n", i, ans[i]);
return 0;
}