【比赛链接】
【题解链接】
**【A】**Andryusha and Colored Balloons
【思路要点】
- 显然答案有下界: ,其中 为点 的度数。
- 我们用构造的方式来说明这个下界是可以取到的。
- 将度数最大的点作为根节点,将根节点和与其相邻的节点染上不同的颜色。
- 按深度优先遍历整棵树,在到一个节点后,我们将确定其儿子的颜色。
- 由题,儿子的颜色互不相同,且不等于当前节点及其父亲节点的颜色。
- 不难发现我们不会使用超过 种颜色。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, ans[MAXN];
vector <int> a[MAXN];
void dfs(int pos, int fa, int col, int dol) {
int tcol = 1;
for (unsigned i = 0; i < a[pos].size(); i++)
if (a[pos][i] != fa) {
int tmp = a[pos][i];
while (tcol == col || tcol == dol) tcol++;
ans[tmp] = tcol;
dfs(tmp, pos, dol, tcol);
tcol++;
}
}
int main() {
read(n);
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);
}
int Max = 0;
for (int i = 1; i <= n; i++)
if (a[i].size() >= Max) Max = a[i].size() + 1;
writeln(Max);
for (int i = 1; i <= n; i++)
if (a[i].size() + 1 == Max) {
ans[i] = 1;
for (unsigned j = 0; j < a[i].size(); j++) {
int tmp = a[i][j];
ans[tmp] = j + 2;
dfs(tmp, i, 1, j + 2);
}
for (int j = 1; j <= n; j++)
printf("%d ", ans[j]);
break;
}
return 0;
}
**【B】**Innokenty and a Football League
【思路要点】
- 看懂题比做对它更难。
- 令 表示以第一种方式命名 得到的结果, 表示以第二种方式命名 得到的结果。
- 若 ,由题, 和 的命名均只能取 和 ,并且若此时还有 , 的命名也只能取 。
- 用一个类似于BFS的过程来模拟上述命名即可。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
map <string, int> mp, fb, home;
string a[MAXN], b[MAXN], ans[MAXN];
int main() {
int n; read(n);
for (int i = 1; i <= n; i++) {
string x, y;
cin >> x >> y;
a[i] = x[0];
a[i] += x[1];
a[i] += x[2];
b[i] = x[0];
b[i] += x[1];
b[i] += y[0];
mp[a[i]]++;
}
static int q[MAXN];
int l = 0, r = -1;
for (int i = 1; i <= n; i++)
if (mp[a[i]] == 1) home[a[i]] = i;
else q[++r] = i;
while (l <= r) {
int tmp = q[l++];
if (fb[b[tmp]]) {
printf("NO\n");
return 0;
}
fb[b[tmp]]++;
ans[tmp] = b[tmp];
if (home[b[tmp]]) q[++r] = home[b[tmp]];
}
printf("YES\n");
for (int i = 1; i <= n; i++)
if (ans[i] == "") cout << a[i] << endl;
else cout << ans[i] << endl;
return 0;
}
**【C】**Underground Lab
【思路要点】
- 条路径的长度总和为 。
- 取一棵原图的生成树,构造其任意一个欧拉序列 。
- 显然 的长度在 以内,并且 与 相邻。
- 将其等分为 段输出即可。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 400005;
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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, m, k;
int f[MAXN];
int len, ans[MAXN];
vector <int> a[MAXN];
int F(int x) {
if (f[x] == x) return x;
else return f[x] = F(f[x]);
}
void work(int pos, int fa) {
ans[++len] = pos;
for (unsigned i = 0; i < a[pos].size(); i++)
if (a[pos][i] != fa) {
work(a[pos][i], pos);
ans[++len] = pos;
}
}
int main() {
read(n), read(m), read(k);
for (int i = 1; i <= n; i++)
f[i] = i;
for (int i = 1; i <= m; i++) {
int x, y; read(x), read(y);
if (F(x) != F(y)) {
a[x].push_back(y);
a[y].push_back(x);
f[F(x)] = F(y);
}
}
work(1, 0);
int limit = min(2 * n / k + (2 * n % k != 0), len), now = 0;
for (int i = 1; i <= k; i++) {
int r = min(len, limit + now);
int l = r - limit + 1;
printf("%d ", limit);
for (int j = l; j <= r; j++)
printf("%d ", ans[j]);
printf("\n");
now = r;
}
return 0;
}
**【D】**Axel and Marston in Bitland
【思路要点】
- 令 表示题目要求的路径颜色序列的前 项组成的字符串, 表示 每一项颜色取反的结果。
- 有 。
- 记 表示 和 之间是否存在路径序列为 的路径, 表示 和 之间是否存在路径序列为 的路径,转移时枚举走完 步后到达的点即可。
- 注意到转移矩阵乘法的本质,这里的矩阵乘法可以被 压位优化。
- 求解答案则是一个矩阵乘向量的过程,由于复杂度较低,不必优化。
- 时间复杂度 ,其中 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 505;
const int MAXLOG = 61;
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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
bool mp[MAXLOG][2][MAXN][MAXN];
bitset <MAXN> row[2][MAXN], col[2][MAXN];
int main() {
int n, m; read(n), read(m);
for (int i = 1; i <= m; i++) {
int x, y, z;
read(x), read(y), read(z);
mp[0][z][x][y] = true;
}
for (int p = 1; p < MAXLOG; p++) {
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
row[0][i][j] = mp[p - 1][0][i][j];
row[1][i][j] = mp[p - 1][1][i][j];
col[0][j][i] = mp[p - 1][0][i][j];
col[1][j][i] = mp[p - 1][1][i][j];
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
mp[p][0][i][j] = (row[0][i] & col[1][j]).count() != 0;
mp[p][1][i][j] = (row[1][i] & col[0][j]).count() != 0;
}
}
bool type = 0; long long ans = 0;
static bool now[MAXN], tmp[MAXN];
now[1] = true;
for (int p = MAXLOG - 1; p >= 0; p--) {
memset(tmp, false, sizeof(tmp));
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
tmp[j] |= now[i] & mp[p][type][i][j];
bool succeed = false;
for (int i = 1; i <= n; i++)
succeed |= tmp[i];
if (succeed) {
ans += 1ll << p; type ^= true;
memcpy(now, tmp, sizeof(tmp));
}
}
if (ans > 1e18) printf("-1\n");
else writeln(ans);
return 0;
}
**【E】**Andryusha and Nervous Barriers
【思路要点】
- 用线段树模拟即可。
- 具体而言,维护一个序列,每个元素为一个堆,堆中元素为当前位置每一种球的个数和下落高度,按照下落高度从低到高排序,初始时每个堆中都有一种个数为1,下落高度为 的球。
- 将隔板从高到低排序,依次处理每一块隔板带来的影响。
- 注意到每一块隔板只会新产生两种球,因此总共的球的种类数是 的。
- 用线段树维护区间中球的最低下落高度即可快速找到每一块隔板会接住的球。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int P = 1e9 + 7;
const int INF = 2e9;
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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
struct ball {int cnt, height; };
bool operator < (ball a, ball b) {return a.height > b.height; }
struct SegmentTree {
struct Node {
int lc, rc;
int Min;
} a[MAXN * 2];
int n, root, size;
priority_queue <ball> Heap[MAXN];
void update(int root) {
a[root].Min = min(a[a[root].lc].Min, a[a[root].rc].Min);
}
void build(int &root, int l, int r, int h) {
root = ++size;
if (l == r) {
Heap[l].push((ball) {1, h + 1});
a[root].Min = h + 1;
return;
}
int mid = (l + r) / 2;
build(a[root].lc, l, mid, h);
build(a[root].rc, mid + 1, r, h);
update(root);
}
void init(int h, int w) {
n = w;
root = size = 0;
build(root, 1, w, h);
}
int query(int root, int u, int l, int r, int ql, int qr, int s) {
int ans = 0;
if (l == ql && r == qr) {
if (l == r) {
while (!Heap[l].empty() && Heap[l].top().height <= u + s) {
ans = (ans + Heap[l].top().cnt) % P;
Heap[l].pop();
}
if (Heap[l].empty()) a[root].Min = INF;
else a[root].Min = Heap[l].top().height;
return ans;
}
int mid = (l + r) / 2;
if (u + s >= a[a[root].lc].Min) ans += query(a[root].lc, u, l, mid, ql, min(mid, qr), s);
if (u + s >= a[a[root].rc].Min) ans += query(a[root].rc, u, mid + 1, r, max(mid + 1, ql), qr, s);
} else {
int mid = (l + r) / 2;
if (mid >= ql) ans += query(a[root].lc, u, l, mid, ql, min(mid, qr), s);
if (mid + 1 <= qr) ans += query(a[root].rc, u, mid + 1, r, max(mid + 1, ql), qr, s);
}
update(root);
return ans % P;
}
void modify(int root, int l, int r, int pos, ball d) {
if (l == r) {
Heap[l].push(d);
a[root].Min = Heap[l].top().height;
return;
}
int mid = (l + r) / 2;
if (mid >= pos) modify(a[root].lc, l, mid, pos, d);
else modify(a[root].rc, mid + 1, r, pos, d);
update(root);
}
void modify(int u, int l, int r, int s) {
int tmp = query(root, u, 1, n, l, r, s);
if (l == 1) modify(root, 1, n, r + 1, (ball) {tmp, u});
else modify(root, 1, n, l - 1, (ball) {tmp, u});
if (r == n) modify(root, 1, n, l - 1, (ball) {tmp, u});
else modify(root, 1, n, r + 1, (ball) {tmp, u});
}
int getans() {
int ans = 0;
for (int i = 1; i <= n; i++) {
while (!Heap[i].empty()) {
ans = (ans + Heap[i].top().cnt) % P;
Heap[i].pop();
}
}
return ans;
}
} ST;
struct info {int u, l, r, s; } a[MAXN];
bool cmp(info a, info b) {
return a.u > b.u;
}
int main() {
int h, w, n;
read(h), read(w), read(n);
for (int i = 1; i <= n; i++) {
read(a[i].u), read(a[i].l);
read(a[i].r), read(a[i].s);
}
sort(a + 1, a + n + 1, cmp);
ST.init(h, w);
for (int i = 1; i <= n; i++)
ST.modify(a[i].u, a[i].l, a[i].r, a[i].s);
writeln(ST.getans());
return 0;
}
**【F】**Intranet of Buses
【思路要点】
- 二分答案 ,我们需要判断是否存在某一时刻公交车 和公交车 、公交车 和公交车 ……公交车 和公交车 的距离均在 以内。
- 令 为周长除以 , 表示 时刻公交车 的位置, 表示 时刻公交车 的位置, 。
- 如果 ,我们称时刻 为好的,若存在时刻 使得时刻 都是好的,那么答案为存在,否则为不存在。
- 我们发现 是一个关于 的分段函数,当公交车 和公交车 都在多边形的某一条边上时, 的横纵坐标都随 线性变化,因此 每一段的轨迹都是一条线段。
- 时刻 是好的,当且仅当 在以 为圆心, 为半径的圆内(或圆上)。
- 一条线段在圆内的部分可以通过圆线求交得到。
- 注意线段退化成点的情况需要特殊处理。
- 这样,我们就得到了哪些时刻是好的(一段段区间)。
- 扫描一遍,找到是否存在时刻 使得时刻 都是好的即可。
- 时间复杂度 ,其中 为题目的精度要求。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
const double eps = 1e-15;
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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
struct point {double x, y; };
point operator + (point a, point b) {return (point) {a.x + b.x, a.y + b.y}; }
point operator - (point a, point b) {return (point) {a.x - b.x, a.y - b.y}; }
point operator * (point a, double b) {return (point) {a.x * b, a.y * b}; }
double operator * (point a, point b) {return a.x * b.y - a.y * b.x; }
double operator / (point a, point b) {
if (abs(b.x) > eps) return a.x / b.x;
else return a.y / b.y;
}
double moo(point a) {return sqrt(a.x * a.x + a.y * a.y); }
point unit(point a) {return a * (1.0 / moo(a)); }
double dist(point a, point b) {return moo(b - a); }
point p[MAXN];
int n, m; double c;
void advance(int &from, int &to, point &pos, double dis) {
if (dist(pos, p[to]) < eps) from++, to++;
while (dis > eps) {
point dir = unit(p[to] - p[from]);
double len = min(dis, dist(p[to], pos));
pos = pos + dir * len;
if (dist(pos, p[to]) < eps) from++, to++;
dis -= len;
}
}
bool check(double dis) {
static vector <pair <double, int> > ans;
ans.clear();
int fromx = 1, tox = 2; point x = p[1];
int fromy = 1, toy = 2; point y = p[1];
advance(fromy, toy, y, c);
double nxtshift = c;
while (fromx != n + 1) {
double len = min(nxtshift, min(dist(x, p[tox]), dist(y, p[toy])));
point s = y - x;
advance(fromx, tox, x, len);
advance(fromy, toy, y, len);
point t = y - x;
double l = (c - nxtshift) / c;
double r = (c - nxtshift + len) / c;
if (len > eps) {
if (dist(s, t) < eps) {
if (moo(s) <= dis) {
ans.push_back(make_pair(l + eps, 1));
ans.push_back(make_pair(r - eps, -1));
}
} else {
if (moo(s) <= dis && moo(t) <= dis) {
ans.push_back(make_pair(l + eps, 1));
ans.push_back(make_pair(r - eps, -1));
} else {
point o = (point) {0, 0}, v = t - s;
swap(v.x, v.y); v.x *= -1;
double e = (s - o) * (t - o);
double f = (t - v) * (s - v);
point pos = (o * f + v * e) * (1 / (e + f));
double postoline = moo(pos);
if (postoline < dis) {
double tmp = sqrt(dis * dis - postoline * postoline);
point posl = pos + unit(s - t) * tmp;
point posr = pos + unit(t - s) * tmp;
double vl = (posl - s) / (t - s);
double vr = (posr - s) / (t - s);
chkmax(vl, 0.0);
chkmax(vr, 0.0);
chkmin(vl, 1.0);
chkmin(vr, 1.0);
ans.push_back(make_pair(l + (r - l) * vl + eps, 1));
ans.push_back(make_pair(l + (r - l) * vr - eps, -1));
}
}
}
}
nxtshift -= len;
if (nxtshift < eps) nxtshift = c;
}
sort(ans.begin(), ans.end());
int sum = 0;
for (unsigned i = 0; i < ans.size(); i++) {
sum += ans[i].second;
if (sum == m) return true;
}
return false;
}
int main() {
read(n), read(m);
for (int i = 1; i <= n; i++) {
read(p[i].x), read(p[i].y);
p[i + n] = p[i];
}
for (int i = 1; i <= n; i++)
c += dist(p[i], p[i + 1]);
c /= m;
double l = 0, r = c;
while (l + 1e-10 < r) {
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
printf("%.10lf\n", (l + r) / 2);
return 0;
}