Codeforces Round #715 (Div. 1)
Codeforces Round #715 (Div. 1)
A. Binary Literature
有三个\(01\)字符串,要么有至少两个字符串\(0\)的个数大于等于\(1\)的个数,要么至少有两个字符串\(1\)的个数大于\(0\)的个数,找到那两个字符串,那么取\(n\)个\(0/1\),然后把剩下的补上去即可
#include<bits/stdc++.h>
using namespace std;
#define Re register int
typedef long long ll;
const int N = 200005;
int T, n, m, cnt1, cnt2, cnt3, t;
char s1[N], s2[N], s3[N];
inline int read()
{
char c = getchar();
int ans = 0;
bool f = 1;
while (c < 48 || c > 57)
{
if (c == '-') f = 0;
c = getchar();
}
while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
return f ? ans : -ans;
}
int main()
{
T = read();
while (T--)
{
n = read(), m = n << 1, cnt1 = cnt2 = cnt3 = 0;
scanf("%s", s1), scanf("%s", s2), scanf("%s", s3);
for (Re i = 0; i < m; ++i)
{
(s1[i] ^ 48) ? ++cnt1 : --cnt1;
(s2[i] ^ 48) ? ++cnt2 : --cnt2;
(s3[i] ^ 48) ? ++cnt3 : --cnt3;
}
t = (cnt1 >= 0) + (cnt2 >= 0) + (cnt3 >= 0);
if (t >= 2)
{
if (cnt1 < 0) for (Re i = 0; i < m; ++i) s1[i] = s3[i];
else if (cnt2 < 0) for (Re i = 0; i < m; ++i) s2[i] = s3[i];
}
else
{
if (cnt1 > 0) for (Re i = 0; i < m; ++i) s1[i] = s3[i];
else if (cnt2 > 0) for (Re i = 0; i < m; ++i) s2[i] = s3[i];
}
t = (t >= 2) ? 49 : 48;
int j = 0, k = 0;
for (Re i = 0; i < n; ++i)
{
while (j < m && s1[j] ^ t) putchar(s1[j++]);
while (k < m && s2[k] ^ t) putchar(s2[k++]);
putchar(t);
++j, ++k;
}
while (j < m) putchar(s1[j++]);
while (k < m) putchar(s2[k++]);
putchar('\n');
}
return 0;
}
B. Almost Sorted
由于它要满足\(a_{i+1} \geq a_{i}-1\),把一个合法序列按每次要递减时为开头分成若干段,每一段都是连续递减,且每次只减\(1\),直到减到上一段的结尾\(+1\),所以\(n\)个数构成的合法序列一共是\(2^{n}\)种,然后预处理下二进制,后面正常做就行
#include<bits/stdc++.h>
using namespace std;
#define Re register int
typedef long long ll;
const int N = 100005;
int T, n, a[N];
ll k, g[N];
inline ll read()
{
char c = getchar();
ll ans = 0;
while (c < 48 || c > 57) c = getchar();
while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
return ans;
}
inline void write(int x)
{
int num = 0;
char sc[15];
while (x) sc[++num] = x % 10 + 48, x /= 10;
while (num) putchar(sc[num--]);
putchar(' ');
}
int main()
{
T = read(), g[0] = g[1] = 1;
for (Re i = 2; i <= 60; ++i) g[i] = g[i - 1] << 1;
while (T--)
{
n = read(), k = read();
if (g[n] && k > g[n])
{
puts("-1");
continue;
}
for (Re i = 1; i <= n; ++i)
if (!g[n - i] || g[n - i] >= k) a[i] = i;
else
for (Re j = i; j <= n; ++j)
if (g[n - j] >= k)
{
for (Re l = i; l <= j; ++l) a[l] = i + j - l;
i = j;
break;
}
else k -= g[n - j];
for (Re i = 1; i <= n; ++i) write(a[i]);
putchar('\n');
}
return 0;
}
C. Complete the MST
可以先求得未知边的边权的异或和,令未知边中仅一条边的值为这个异或和,其他均为\(0\),这样一定是最优的,不选那条边权值为异或和的边时,选\(0\)一定满足最小生成树最优,在一定要选那条边权值为异或的边和即所有未知边的边都要选时,最小生成树是要边权加起来,这样也是最优的
容易发现在数据范围较大时,这条边权为异或值的边一定能不被选到,考虑如果可能要选到边权为异或和的边时,一定是在\(\frac{n \cdot (n-1)}{2} - m < n\)范围内,即此时未知边的个数不超过最小生成树的边数,这样就可能选中全部的未知边,发现此时的\(n\)较小,可以先选出\(m\)条边中可能用到的\(n-1\)条边,然后取出所有未知边,枚举哪条未知边的边权为权值异或和,复杂度为\(\Theta(m log\ m +n^{2})\),但此时\(n\)最大不超过\(633\),所以可以较快通过
当\(\frac{n \cdot (n-1)}{2} - m \geq n\)时,此时一定可以让一条不选则的边的边权为异或和,所以就是要在所有未知边的基础上,使得选择到的\(m\)条边中的边权和尽量小,可以\(Kruskal\)去做,先求出所有未知边构成的并查集情况,然后把那\(m\)条边排序后按\(Kruskal\)的方法做,求出所有未知边构成的并查集情况,可以对于一个未知并查集情况的点,将这个点通过未知边拓展出去,直到不能再拓展了,但是这样暴力做是\(\Theta(n^{2})\)的,原因是一个点可能会被多条边指向,考虑链表优化,这样一个点如果被遍历过后,这个点将不会被其他点指向,总的复杂度是\(\Theta(m log\ m + n + m)\)
#include<bits/stdc++.h>
using namespace std;
#define Re register int
typedef long long ll;
const int N = 200005, M = 900;
struct edg
{
int l, r, s;
}eg[N], egg[N << 1], a[M];
int n, m, s, t, cnt, fa[N], pr[N], nt[N];
bool b[M][M];
ll ans;
vector<int> gg[N];
inline int read()
{
char c = getchar();
int ans = 0;
while (c < 48 || c > 57) c = getchar();
while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
return ans;
}
inline bool cmp(edg x, edg y)
{
return x.s < y.s;
}
inline bool cmp1(edg x, edg y)
{
return x.r < y.r;
}
inline int find_fa(int x)
{
return (fa[x] ^ x) ? fa[x] = find_fa(fa[x]) : x;
}
inline int find_nt(int x)
{
return (fa[nt[x]]) ? nt[x] = find_nt(nt[x]) : nt[x];
}
inline void dfs(int x, int y)
{
fa[x] = y, nt[pr[x]] = nt[x], pr[nt[x]] = pr[x];
int u = gg[x].size(), v = 0, w = find_nt(0);
while (v < u && w <= n)
{
int uu = gg[x][v];
while (w < uu) dfs(w, y), w = find_nt(w);
if (w == uu) w = find_nt(w);
++v;
}
while (w <= n) dfs(w, y), w = find_nt(w);
}
int main()
{
n = read(), m = read();
for (Re i = 1; i <= m; ++i) eg[i].l = read(), eg[i].r = read(), eg[i].s = read(), s ^= eg[i].s;
sort(eg + 1, eg + m + 1, cmp);
if (1ll * n * (n - 1) / 2 < 1ll * n + m)
{
ans = 1e14;
sort(eg + 1, eg + m + 1, cmp);
for (Re i = 1; i <= m; ++i) b[eg[i].l][eg[i].r] = 1;
for (Re i = 1; i < n; ++i)
for (Re j = i + 1; j <= n; ++j)
if (!b[i][j] && !b[j][i]) a[++t].l = i, a[t].r = j;
int mm = 0;
for (Re i = 1; i <= n; ++i) fa[i] = i;
for (Re i = 1, j = n - 1; i <= m; ++i)
{
int u = eg[i].l, v = eg[i].r;
if (find_fa(u) ^ find_fa(v))
{
eg[++mm] = eg[i], fa[fa[u]] = fa[v], --j;
if (!j) break;
}
}
m = mm;
for (Re i = 1; i <= t; ++i)
{
for (Re j = 1; j <= n; ++j) fa[j] = j;
int w = n - 1;
ll tot = 0;
for (Re j = 1; j < i; ++j)
{
int u = a[j].l, v = a[j].r;
if (find_fa(u) ^ find_fa(v))
{
fa[fa[u]] = fa[v], --w;
if (!w)
{
putchar(48);
return 0;
}
}
}
for (Re j = i + 1; j <= t; ++j)
{
int u = a[j].l, v = a[j].r;
if (find_fa(u) ^ find_fa(v))
{
fa[fa[u]] = fa[v], --w;
if (!w)
{
putchar(48);
return 0;
}
}
}
for (Re j = 1; j <= m; ++j)
{
if (eg[j - 1].s <= s && eg[j].s > s && find_fa(a[i].l) ^ find_fa(a[i].r)) fa[fa[a[i].l]] = fa[a[i].r], --w, tot += s;
int u = eg[j].l, v = eg[j].r;
if (find_fa(u) ^ find_fa(v))
{
fa[fa[u]] = fa[v], --w, tot += eg[j].s;
if (!w || tot >= ans) break;
}
}
if (w) tot += s;
if (tot < ans) ans = tot;
}
}
else
{
for (Re i = 1; i <= m; ++i)
egg[i] = eg[i], egg[i + m].l = eg[i].r, egg[i + m].r = eg[i].l;
sort(egg + 1, egg + (m << 1) + 1, cmp1);
for (Re i = 1; i <= (m << 1); ++i) gg[egg[i].l].push_back(egg[i].r);
for (Re i = 0; i <= n + 1; ++i) pr[i] = i - 1, nt[i] = i + 1;
for (Re i = 1; i <= n; ++i)
if (!fa[i]) ++t, dfs(i, i);
--t;
if (!t)
{
putchar(48);
return 0;
}
for (Re i = 1; i <= m; ++i)
{
int u = eg[i].l, v = eg[i].r;
if (find_fa(u) ^ find_fa(v))
{
fa[fa[u]] = fa[v], ans += eg[i].s, --t;
if (!t) break;
}
}
}
printf("%lld", ans);
return 0;
}
D. Swap Pass
对于一个数列\(i,a_{i},a_{a_{i}}...\)最后一定能回到\(i\),称这个为一个循环,处理一个循环,只需要任意选中循环中的一个点,然后让这个点一直将它上面的\(a\)值换到正确的位置上即可,但是不同循环之间的操作可能会相交
考虑怎么把两个循环合并起来,只需要在他们之间连一条边,并且这条边不能与操作中的边有相交即可
考虑将所有循环合并起来,每次碰到一个新的循环就与原先的循环合并即可
考虑怎么使得合并循环的这些边与操作中的边不相交
取一个操作中心,其他点按与这个点的连边的斜率排序,合并两个循环时只要合并排序后相邻两个不同循环上的点即可
复杂度是\(\Theta(n log\ n)\)
#include<bits/stdc++.h>
using namespace std;
#define Re register int
typedef long long ll;
const int N = 2005;
struct info
{
int x, y, t, id;
}a[N];
int n, t, res, fa[N], g[N], ansl[N * N], ansr[N * N];
bool b[N];
inline int read()
{
char c = getchar();
int ans = 0;
bool f = 1;
while (c < 48 || c > 57)
{
if (c == '-') f = 0;
c = getchar();
}
while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
return f ? ans : -ans;
}
inline void write(int x)
{
if (x < 0) putchar('-'), x = -x;
int num = 0;
char sc[15];
if (!x) sc[num = 1] = 48;
while (x) sc[++num] = x % 10 + 48, x /= 10;
while (num) putchar(sc[num--]);
}
inline void dfs(int x, int y)
{
fa[x] = y;
if (x ^ y) b[y] = 1;
if (!fa[a[x].t]) dfs(a[x].t, y);
}
inline int find_fa(int x)
{
return (fa[x] ^ x) ? fa[x] = find_fa(fa[x]) : x;
}
inline bool cmp(info x, info y)
{
return (b[fa[x.t]] ^ b[fa[y.t]]) ? b[fa[x.t]] > b[fa[y.t]] : 1ll * (x.y - a[1].y) * (y.x - a[1].x) > 1ll * (y.y - a[1].y) * (x.x - a[1].x);
}
int main()
{
n = read();
for (Re i = 1; i <= n; ++i) a[i].x = read(), a[i].y = read(), a[i].t = read(), a[i].id = i;
for (Re i = 1; i <= n; ++i)
if (!fa[i]) dfs(i, i);
for (Re i = 1; i <= n; ++i)
if (a[i].id ^ a[i].t && (!t || a[i].x < a[t].x || (a[i].x == a[t].x && a[i].y < a[t].y))) t = i;
if (!t)
{
putchar(48);
return 0;
}
if (t > 1) swap(a[1], a[t]);
sort(a + 2, a + n + 1, cmp);
for (Re i = 3; i <= n; ++i)
if (b[fa[a[i].t]])
{
if (find_fa(a[i - 1].t) == find_fa(a[i].t)) continue;
ansl[++res] = a[i - 1].id, ansr[res] = a[i].id, fa[fa[a[i - 1].t]] = fa[a[i].t], swap(a[i - 1].t, a[i].t);
}
else break;
for (Re i = 2; i <= n; ++i) g[a[i].id] = i;
while (a[1].id ^ a[1].t) ansl[++res] = a[1].id, ansr[res] = a[1].t, a[1].t = a[g[a[1].t]].t;
printf("%d\n", res);
for (Re i = 1; i <= res; ++i) write(ansl[i]), putchar(' '), write(ansr[i]), putchar('\n');
return 0;
}
E. Tree Calendar
个人感觉思维难度比\(D\)简单,但是代码量比\(D\)大,而且细节较多
可以发现你再怎么操作,对于叶子节点,他们的相对大小是固定的,可以先找出最初的\(dfs\)序,并判断这个\(dfs\)序合不合法
记当前根节点的\(dfs\)序值为\(x\)
如果\(x=1\),和最初树的比较,看是否一样
否则,对于值\(1\)到\(x-2\),一定是移动到不能移动,每次移动到的点的所有儿子的值一定是之前就移动到的值,并且假设当前移动的值为\(y\),则\(y\)移动到的位置一定是\(y-1\)移动到的位置的父亲节点所在的子树内
移完\(1\)到\(x-2\)后可以找出当前所有点的\(dfs\)值,然后暴力移动\(x-1\),看能不能移动到你目标的\(dfs\)序
如果满足题意是\(YES\)的话,移动的次数就是\(1~x-1\)每个点的深度之和再减去\(x-1\),因为每个点都是从深度为\(1\)的根节点开始移动的
时间复杂度为\(\Theta(n log\ n)\)
#include<bits/stdc++.h>
using namespace std;
#define Re register int
typedef long long ll;
const int N = 300005;
int n, cnt, t, tt, res, tim, hea[N], nxt[N << 1], to[N << 1], a[N], c[N], id[N], fa[N], dep[N], dfn_now[N], dfn_cur[N], dfn_ans[N];
bool b[N];
ll ans;
vector<int> son[N];
inline int read()
{
char c = getchar();
int ans = 0;
bool f = 1;
while (c < 48 || c > 57)
{
if (c == '-') f = 0;
c = getchar();
}
while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
return f ? ans : -ans;
}
inline void write(ll x)
{
int num = 0;
char sc[25];
if (!x) sc[num = 1] = 48;
while (x) sc[++num] = x % 10 + 48, x /= 10;
while (num) putchar(sc[num--]);
putchar(' ');
}
inline void add(int x, int y)
{
nxt[++cnt] = hea[x], to[cnt] = y, hea[x] = cnt;
}
inline bool cmp_now(int x, int y)
{
return dfn_now[x] < dfn_now[y];
}
inline bool cmp_ans(int x, int y)
{
return dfn_ans[x] < dfn_ans[y];
}
inline void dfs(int x)
{
dep[x] = dep[fa[x]] + 1, b[x] = 1;
for (Re i = hea[x]; i; i = nxt[i])
{
int u = to[i];
if (u == fa[x]) continue;
fa[u] = x, b[x] = 0;
dfs(u);
}
if (b[x]) a[++res] = x;
}
inline void dfs_check(int x)
{
++tim;
if (dfn_ans[x] ^ tim)
{
puts("NO");
exit(0);
}
int u = 0;
for (Re i = hea[x]; i; i = nxt[i])
if (to[i] ^ fa[x]) c[++u] = to[i];
sort(c + 1, c + u + 1, cmp_ans);
for (Re i = 1; i <= u; ++i) son[x].push_back(c[i]);
for (Re i = 0; i < u; ++i) dfs_check(son[x][i]);
}
inline void dfs1(int x)
{
dfn_cur[x] = ++tim;
int u = son[x].size();
for (Re i = 0; i < u; ++i) dfs1(son[x][i]);
}
int main()
{
n = read();
for (Re i = 1; i <= n; ++i) dfn_now[i] = read(), id[dfn_now[i]] = i;
for (Re i = 1; i < n; ++i)
{
int u = read(), v = read();
add(u, v), add(v, u);
}
dfs(1);
sort(a + 1, a + res + 1, cmp_now);
for (Re i = 1; i <= res; ++i)
{
cnt = 0;
for (Re j = a[i]; j && !dfn_ans[j]; j = fa[j]) c[++cnt] = j;
for (Re j = cnt; j; --j) dfn_ans[c[j]] = ++tim;
}
tim = 0;
dfs_check(1);
if (dfn_now[1] == 1)
{
for (Re i = 2; i <= n; ++i)
if (dfn_now[i] ^ dfn_ans[i])
{
puts("NO");
return 0;
}
puts("YES");
puts("0");
for (Re i = 1; i <= n; ++i) write(dfn_ans[i]);
}
t = 1;
for (Re i = 1, j = 0; i < dfn_now[1] - 1; ++i)
{
int u = id[i], v = son[u].size();
if (dep[u] < dep[t])
{
puts("NO");
return 0;
}
for (Re k = 0; k < v; ++k)
if (!dfn_cur[son[u][k]])
{
puts("NO");
return 0;
}
if (b[u]) ++j;
if (b[u] && u ^ a[j])
{
puts("NO");
return 0;
}
ans += dep[u] - 1, dfn_cur[u] = i, t = fa[u];
}
cnt = 0, tim = dfn_now[1] - 2;
for (Re i = t; i; i = fa[i]) c[++cnt] = i;
for (Re i = cnt; i; --i) dfn_cur[c[i]] = ++tim;
for (Re i = 1; i <= cnt; ++i)
{
int u = son[c[i]].size();
for (Re j = 0; j < u; ++j)
if (!dfn_cur[son[c[i]][j]]) dfs1(son[c[i]][j]);
}
t = dep[id[dfn_now[1] - 1]], tt = 1, ans += t - 1;
while (dep[tt] < t)
{
int u = son[tt].size();
bool pd = 0;
for (Re i = 0; i < u; ++i)
if (dfn_cur[son[tt][i]] > dfn_cur[tt])
{
swap(dfn_cur[tt], dfn_cur[son[tt][i]]), tt = son[tt][i], pd = 1;
break;
}
if (!pd)
{
puts("NO");
return 0;
}
}
for (Re i = 1; i <= n; ++i)
if (dfn_now[i] ^ dfn_cur[i])
{
puts("NO");
return 0;
}
puts("YES");
write(ans), putchar('\n');
for (Re i = 1; i <= n; ++i) write(dfn_ans[i]);
return 0;
}
F. Optimal Encoding
这题题意就很绕,知道完题意后感觉更绕了。。
正解似乎挺难的,在那场比赛的题解下面讨论区看到了ecnerwala大神比较简单且好写的做法,后面又去看了yhx大神超级简短的代码
对于点\(i\),它往左的边最多就两条,一条指向前驱,一条指向后继,往右的边也一样
对于一个区间\([l,r]\)中的某个点\(i\)来说,可以看成是两个区间\([l,i]\)和\([i,r]\),又因为一条边是相互的,所以仅看成\([l,i]\)即可
对与\(i\)这个点以及区间\([l,i]\),从\(i+1\)开始往\(l\)判断这条边是否需要连接
对于点对\((i,j)\),判断它们是否需要连接判断一次即可,所以最多只会进行\(n^{2}\)级别的的判断操作
如果要连边当且仅当这条边这两个点的值都会更改对方的左边/右边的前驱/后继值
由于边是相互的,模拟过程可以发现,判断只要判断单向的就可以了
复杂度是\(\Theta(n^{2})\)
#include<bits/stdc++.h>
using namespace std;
#define Re register int
const int N = 25005;
int n, m, ans, a[N], d[N], lpre[N], lsuf[N], rpre[N], rsuf[N];
inline int read()
{
char c = getchar();
int ans = 0;
while (c < 48 || c > 57) c = getchar();
while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
return ans;
}
inline void write(int x)
{
int num = 0;
char sc[15];
if (!x) sc[num = 1] = 48;
while (x) sc[++num] = x % 10 + 48, x /= 10;
while (num) putchar(sc[num--]);
putchar('\n');
}
inline void add1(int x, int y)
{
++ans, rsuf[x] = y, lpre[y] = x;
}
inline void add2(int x, int y)
{
++ans, rpre[x] = y, lsuf[y] = x;
}
inline void cut1(int x, int y)
{
ans -= (rsuf[x] == y && lpre[y] == x);
}
inline void cut2(int x, int y)
{
ans -= (rpre[x] == y && lsuf[y] == x);
}
inline void calc(int x, int y)
{
if (a[x] < a[y])
{
if (a[x] > a[lpre[y]]) cut1(lpre[y], y), cut1(x, rsuf[x]), add1(x, y);
}
else
{
if (a[x] < a[lsuf[y]]) cut2(lsuf[y], y), cut2(x, rpre[x]), add2(x, y);
}
}
int main()
{
n = read(), m = read(), a[n + 1] = n + 1;
for (Re i = 1; i <= n; ++i) a[i] = read(), d[i] = i, lsuf[i] = rsuf[i] = n + 1;
while (m--)
{
int u = read(), v = read();
int l = u, r = v, s = v + 1;
while (l <= r)
{
int mid = l + r >> 1;
if (d[mid] > u) s = mid, r = mid - 1;
else l = mid + 1;
}
for (Re i = s; i <= v; ++i)
while (d[i] > u) calc(--d[i], i);
write(ans);
}
return 0;
}

浙公网安备 33010602011771号