Codeforces Round #715 (Div. 1)
Codeforces Round #715 (Div. 1)
Codeforces Round #715 (Div. 1)
A
三个串中必定存在两个串 ,满足
可以反证如果不存在就很离谱
也就是说我们构造一个 ,使得 是 的子序列,可以共用的部分 ,那么最终的长度
B
其实每个符合条件的序列可以被划分为若干段,以 为横坐标, 为纵坐标,就是这样:
每一段都是斜率为 的线段且相邻段首尾相接。那么如果长度和切割点一定,这个序列是唯一的。这就是一个简单的组合数。然后可以像进制转换那样去确定每一个切割点...
#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
补图中只要一条边有权值 就可以满足异或为 的要求。先处理补图的连通性。如果有某条边不影响连通性,就把权值赋到这条边上,不用算入答案(不会出现在生成树中),然后把确定的边加入执行 的过程。否则(一棵树)要把 算入答案,然后先把确定边加入,再类似次小生成树处理一个替换调整的过程,可以跳 或再弄一个并查集来维护
如何处理补图的连通性?肯定存在一个点 ,度数 ,那么补图中不与 直接相连的点 ,对于这些点暴力判断,复杂度即为线性。也可以用一种边删边处理的方法解决。
D
把 当成边,如果整个排列构成一个环,那么只需要随便选一个点,然后不断把当前点上的值归位即可,就像这样:
我们将其称之为“太阳线”,一个端点引出的线段显然没有交。
现在考虑多个环的情况,可以交换两个不同环上的两个点来合并成一个环。但交换会连出一些新的线,有可能会和“太阳线”相交。为了避免这种情况,我们先选定“中心点”,将其它所有点进行极角排序,对两个相邻点执行并查集的过程:如果在一个环中跳过,否则这两个环以这两点为媒介相连。容易发现这样构造不会和“太阳线”相交。
为了使极角排序简单顺利进行,不妨选最左点。
#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\n", cnt);
for (int i = 1; i <= cnt; ++i)
printf ("%d %d\n", o[i].x, o[i].y);
return 0;
}
E
每次操作会选一个 和 中权值最小的点 交换且 ,那么交换后 依旧是最小的,那么操作不改变兄弟节点间的相对大小。借此可以确定树的 序
可以把操作分为若干轮,第 轮把数字 从根节点一路往最小的儿子向下走,直到走不了。由此可以得出当前进行的操作次数。当前正在进行的是 轮,由此可以得出当前进行的操作次数为 到 数字所在节点的深度之和。剩下的问题是如何判断无解。
对于数字 ,操作已经结束,每个点所在的位置一定(可自行yy其规律,即代码中的 数组)
对于 ,它的目标节点应当在当前所在节点的子树里,且它能够一直向上到根(即路径上权值都比它大)
对于 所在的位置,它们的爸爸都得比自己小
#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\n%lld\n", 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
考虑对于一个固定的 求出答案。先按照题意暴力的连边 表示限制 。有这些个推导:
1、如果 之间存在长度 的路径,边 无用
2、对于一个 ,必须的边 最多只有一条,左边亦然。因为如果有两条 , 和 同在的区间也包含 ,那么 间也有一条边或路径,根据结论 , 中有一条边无用
3、如何找到这个唯一的 呢?在所有包含 的区间中找到右端点 最大的, 即为区间 中 的后继。 亦然。
4、但结论 中唯一的 也不一定必要,可能存在路径 并根据结论 去掉这条边。如何判断?容易发现这个 必定 ,这次我们找到包含 的左端点 最小的区间,如果存在 ,满足 ,那么这个 可以作为 而使 失效
以上四点可以让我们解决一个固定的 了:先用 求出所有 ,再用 判断哪些 无用,剩下的即为答案。但通过这种方法求出所有 对应的答案似乎太慢了。
In fact,稍稍改进一下结论和做法就可以较快速地求出所有答案。我们维护的 就是向左向右的后继,不妨顺便把前驱也计算出来,把这四条边称为 “判定边”。结论 引理:一条边 不会被删除当且仅当它同时是 的 “判定边”。可以自行证明其充要性。每个点的 “判定边” 的另一端单调,最多改变 次,可以在 时间内维护(直接遍历 每个区间为 ,先把不用更改的区间扔掉再遍历为 ,差别不大),可以通过本题。
而官方题解的优秀做法就是处理一下每条边作为 “判定边” 的时间段然后 +莫队 一顿操作弄到 。反正特别麻烦,所以只写了非官方做法(下面的代码常数较大)
#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\n", res);
}
return 0;
}
本文作者:-敲键盘的猫-
本文链接:https://www.cnblogs.com/whx666/p/715-div1.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 现代计算机视觉入门之:什么是图片特征编码
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· Spring AI + Ollama 实现 deepseek-r1 的API服务和调用
· 数据库服务器 SQL Server 版本升级公告
· C#/.NET/.NET Core技术前沿周刊 | 第 23 期(2025年1.20-1.26)
· 程序员常用高效实用工具推荐,办公效率提升利器!