Codeforces Round #715 (Div. 1)
Codeforces Round #715 (Div. 1)
A
三个串中必定存在两个串 (A,B),满足(max(min(number of 0 in A,number of 0 in B),min(number of 1 in A,number of 1 in B))ge n)
可以反证如果不存在就很离谱
也就是说我们构造一个 (S),使得 (A,B) 是 (S) 的子序列,可以共用的部分 (ge n),那么最终的长度 (leq 3 imes n)
B
其实每个符合条件的序列可以被划分为若干段,以 (i) 为横坐标,(a_i) 为纵坐标,就是这样:
每一段都是斜率为 (-1) 的线段且相邻段首尾相接。那么如果长度和切割点一定,这个序列是唯一的。这就是一个简单的组合数。然后可以像进制转换那样去确定每一个切割点...
#include <bits/stdc++.h>
using namespace std;
#define int long long
void read (int &x) {
char ch = getchar(); int f = 0; x = 0;
while (!isdigit(ch)) { if (ch == '-') f = 1; ch = getchar(); }
while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
if (f) x = -x;
} const int N = 1e5 + 5, lim = 1e5, mx = 1e18;
int n, k, p[N], in[N], num[N];
signed main() {
int T; read (T);
int now = 1;
for (int i = 1; i <= lim; ++i) {
num[i] = now; now *= 2;
if (now > mx) {
for (int j = i + 1; j <= lim; ++j) num[j] = mx * 5;
break;
}
} num[0] = 1;
while (T--) {
read (n), read (k);
if (k > num[n]) { puts ("-1"); continue; }
int p = 1, sum = 0, la = 0;
for (int i = 1; i <= n; ++i) {
sum += num[n - i];
if (sum == k) {
for (int j = i; j > la; --j) printf ("%lld ", j);
for (int j = n; j > i; --j) printf ("%lld ", j); break;
} else if (sum > k) {
sum -= num[n - i];
for (int j = i; j > la; --j) printf ("%lld ", j); la = i;
}
} puts ("");
}
}
C
补图中只要一条边有权值 (X) 就可以满足异或为 (0) 的要求。先处理补图的连通性。如果有某条边不影响连通性,就把权值赋到这条边上,不用算入答案(不会出现在生成树中),然后把确定的边加入执行 (Kruscal) 的过程。否则(一棵树)要把 (X) 算入答案,然后先把确定边加入,再类似次小生成树处理一个替换调整的过程,可以跳 (LCA) 或再弄一个并查集来维护
如何处理补图的连通性?肯定存在一个点 (p),度数 (leq frac{m}{n}),那么补图中不与 (p) 直接相连的点 (leq frac{m}{n}),对于这些点暴力判断,复杂度即为线性。也可以用一种边删边处理的方法解决。
D
把 ((i,a_i)) 当成边,如果整个排列构成一个环,那么只需要随便选一个点,然后不断把当前点上的值归位即可,就像这样:
我们将其称之为“太阳线”,一个端点引出的线段显然没有交。
现在考虑多个环的情况,可以交换两个不同环上的两个点来合并成一个环。但交换会连出一些新的线,有可能会和“太阳线”相交。为了避免这种情况,我们先选定“中心点”,将其它所有点进行极角排序,对两个相邻点执行并查集的过程:如果在一个环中跳过,否则这两个环以这两点为媒介相连。容易发现这样构造不会和“太阳线”相交。
为了使极角排序简单顺利进行,不妨选最左点。
#include <bits/stdc++.h>
using namespace std;
void read (int &x) {
char ch = getchar(); int f = 0; x = 0;
while (!isdigit(ch)) { if (ch == '-') f = 1; ch = getchar(); }
while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
if (f) x = -x;
} const int N = 2020;
int n, p, fa[N], a[N], m, id[N], x[N], y[N]; double k[N];
int get (int x) {
return x == fa[x] ? x : fa[x] = get (fa[x]);
}
void merge (int x, int y) { fa[get (x)] = get (y); }
bool cmp (int a, int b) { return k[a] < k[b]; }
struct ans { int x, y; } o[N * 10]; int cnt;
void work (int x, int y) {
o[++cnt] = {x, y}, swap (a[x], a[y]);
}
signed main() {
read (n); x[0] = 1e9;
for (int i = 1; i <= n; ++i) fa[i] = i;
for (int i = 1; i <= n; ++i) {
read (x[i]), read (y[i]);
read (a[i]), merge (i, a[i]);
if (i != a[i] && x[i] < x[p]) p = i;
}
if (!p) return puts ("0"), 0;
for (int i = 1; i <= n; ++i)
if (i != a[i] && i != p) id[++m] = i;
for (int i = 1; i <= m; ++i) {
k[id[i]] = 1.0 * (y[id[i]] - y[p]) / (x[id[i]] - x[p]);
}
sort (id + 1, id + m + 1, cmp);
for (int i = 1; i < m; ++i) {
int fx = get (id[i]), fy = get (id[i + 1]);
if (fx == fy) continue;
work (id[i], id[i + 1]), fa[fx] = fy;
}
while (a[p] != p) work (p, a[p]);
printf ("%d
", cnt);
for (int i = 1; i <= cnt; ++i)
printf ("%d %d
", o[i].x, o[i].y);
return 0;
}
E
每次操作会选一个 (x) 和 (son(x)) 中权值最小的点 (y) 交换且 (a_x<a_y),那么交换后 (a_y) 依旧是最小的,那么操作不改变兄弟节点间的相对大小。借此可以确定树的 (dfs) 序
可以把操作分为若干轮,第 (i) 轮把数字 (i) 从根节点一路往最小的儿子向下走,直到走不了。由此可以得出当前进行的操作次数。当前正在进行的是 (a_1-1) 轮,由此可以得出当前进行的操作次数为 (1) 到 (a_1-1) 数字所在节点的深度之和。剩下的问题是如何判断无解。
对于数字 ([1,a_1-2]),操作已经结束,每个点所在的位置一定(可自行yy其规律,即代码中的 (st) 数组)
对于 (a_1-1) ,它的目标节点应当在当前所在节点的子树里,且它能够一直向上到根(即路径上权值都比它大)
对于 ([a_1,n]) 所在的位置,它们的爸爸都得比自己小
#include <bits/stdc++.h>
using namespace std;
#define int long long
void read (int &x) {
char ch = getchar(); int f = 0; x = 0;
while (!isdigit(ch)) { if (ch == '-') f = 1; ch = getchar(); }
while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
if (f) x = -x;
} const int N = 3e5 + 5, M = N << 1;
int n, s, a[N], w[N], tot, dfn[N], t[N], d[N], fa[N], o;
vector<pair<int, int> > g[N];
int cnt, h[N], nxt[M], to[M]; int tp, st[N];
void add (int u, int v) {
to[++cnt] = v, nxt[cnt] = h[u], h[u] = cnt;
}
#define pb push_back
void dfs (int u, int la) {
dfn[u] = ++tot, t[tot] = u, d[u] = d[la] + 1, fa[u] = la;
for (int i = h[u], v; i; i = nxt[i])
if ((v = to[i]) != la) g[u].push_back ({a[v], v});
sort (g[u].begin(), g[u].end());
for (auto i : g[u]) dfs (i.second, u); st[++tp] = u;
}
void YES () {
printf ("YES
%lld
", s);
for (int i = 1; i <= n; ++i) printf ("%lld ", dfn[i]);
}
void NO () { puts ("NO"), exit (0); }
void goup (int p) {
int x = w[p];
while (fa[x]) {
if (a[x] > a[fa[x]]) NO ();
swap (a[x], a[fa[x]]), x = fa[x];
}
x = st[p];
while (x) { if (x == w[p]) return; x = fa[x]; }
NO ();
}
signed main() {
read (n);
for (int i = 1; i <= n; ++i) read (a[i]), w[a[i]] = i;
for (int i = 1, u, v; i < n; ++i)
read (u), read (v), add (u, v), add (v, u);
dfs (1, 0); o = a[1];
for (int i = 1; i < o; ++i) s += d[w[i]] - 1;
for (int i = 1; i < o - 1; ++i) if (w[i] != st[i]) NO ();
if (o > 1) goup (o - 1);
for (int i = 1; i <= n; ++i)
if (a[fa[i]] > a[i] && a[i] >= o - 1) NO ();
return YES (), 0;
}
F
考虑对于一个固定的 (k) 求出答案。先按照题意暴力的连边 ((x,y)) 表示限制 (a_x<a_y)。有这些个推导:
1、如果 (u,v) 之间存在长度 (ge 2) 的路径,边 ((u,v)) 无用
2、对于一个 (x),必须的边 ((x,r),r>x) 最多只有一条,左边亦然。因为如果有两条 ((x,r1),(x,r2),x<r1<r2),(x) 和 (r2) 同在的区间也包含 (r1),那么 (r1,r2) 间也有一条边或路径,根据结论 (1),((x,r1)(x,r2)) 中有一条边无用
3、如何找到这个唯一的 (l,r) 呢?在所有包含 (x) 的区间中找到右端点 (R) 最大的,(r) 即为区间 ((x,R]) 中 (a_x) 的后继。(l) 亦然。
4、但结论 (2) 中唯一的 ((x,r)) 也不一定必要,可能存在路径 ((x,dots,t,r)) 并根据结论 (1) 去掉这条边。如何判断?容易发现这个 (t) 必定 (<x),这次我们找到包含 (r) 的左端点 (L) 最小的区间,如果存在 (p),满足 (Lleq p<x,a_x<a_p<a_r),那么这个 (p) 可以作为 (t) 而使 ((x,r)) 失效
以上四点可以让我们解决一个固定的 (k) 了:先用 (3) 求出所有 (l,r),再用 (4) 判断哪些 (l,r) 无用,剩下的即为答案。但通过这种方法求出所有 (k) 对应的答案似乎太慢了。
In fact,稍稍改进一下结论和做法就可以较快速地求出所有答案。我们维护的 (l,r) 就是向左向右的后继,不妨顺便把前驱也计算出来,把这四条边称为 “判定边”。结论 (4) 引理:一条边 ((x,y)) 不会被删除当且仅当它同时是 (x,y) 的 “判定边”。可以自行证明其充要性。每个点的 “判定边” 的另一端单调,最多改变 (O(n)) 次,可以在 (O(n^2)/O(nq)) 时间内维护(直接遍历 每个区间为 (nq),先把不用更改的区间扔掉再遍历为 (n^2),差别不大),可以通过本题。
而官方题解的优秀做法就是处理一下每条边作为 “判定边” 的时间段然后 (set)+莫队 一顿操作弄到 (O(nsqrt{q} logn))。反正特别麻烦,所以只写了非官方做法(下面的代码常数较大)
#include <bits/stdc++.h>
using namespace std;
void read (int &x) {
char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 25005;
int n, m, res, a[N], L[N], R[N], la[N][2], ra[N][2];
void work (int l, int r) {
for (int i = l; i <= r; ++i) {
for (int j = R[i] + 1; j <= r; ++j) {
int t = ra[i][0];
if (a[j] < a[i] && a[j] > a[t])
res += (la[j][1] == i) - (la[t][1] == i), ra[i][0] = j;
t = ra[i][1];
if (a[j] > a[i] && a[j] < a[t])
res += (la[j][0] == i) - (la[t][0] == i), ra[i][1] = j;
R[i] = j;
}
for (int j = L[i] - 1; j >= l; --j) {
int t = la[i][0];
if (a[j] < a[i] && a[j] > a[t])
res += (ra[j][1] == i) - (ra[t][1] == i), la[i][0] = j;
t = la[i][1];
if (a[j] > a[i] && a[j] < a[t])
res += (ra[j][0] == i) - (ra[t][0] == i), la[i][1] = j;
L[i] = j;
}
}
}
signed main() {
read (n), read (m); a[n + 1] = n + 1;
for (int i = 1; i <= n; ++i) read (a[i]);
for (int i = 1; i <= n; ++i)
R[i] = L[i] = i, la[i][1] = ra[i][1] = n + 1;
for (int i = 1, l, r; i <= m; ++i) {
read (l), read (r);
work (l, r); printf ("%d
", res);
}
return 0;
}