比赛传送门:CF #1137

比赛记录:点我

每次都自闭的 div1 啊,什么时候才能上 IM 呢。


【A】Skyscrapers

题意简述:

有一个 \(n\times m\) 的矩阵 \(a_{ij}\)。

对于每个 \((i,j)\)(\(1\le i\le n\),\(1\le j\le m\)),你把第 \(i\) 行和第 \(j\) 列单独抽出,这样就有 \(n+m-1\) 个数被你抽出。

你可以对这些数重新标号为正整数,但是要满足第 \(i\) 行所有数的大小关系不变,第 \(j\) 列所有数的大小关系不变(两个限制相互独立)。

满足这个限制下,你希望最大的标号尽量小,对每个 \((i,j)\) 求出这个最小的最大标号。

题解:

因为行大小关系不变,列大小关系不变,我们考虑分别离散化每行每列,并统计每个数在行内和列内的排名。

若 \((i,j)\) 在行内排名为第 \(x\) 小,列内排名为第 \(y\) 小,它最优情况下就被标号为 \(\max(x,y)\),然后行内、列内比它大的数依次往大排。

由此写出代码:

#include <cstdio>
#include <algorithm> const int MN = 1005; int N, M, A[MN][MN];
int X[MN][MN], Y[MN][MN];
int B[MN], C1[MN], C2[MN], C; int main() {
scanf("%d%d", &N, &M);
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= M; ++j)
scanf("%d", &A[i][j]);
for (int i = 1; i <= N; ++i) {
for (int j = 1; j <= M; ++j) B[j] = A[i][j];
std::sort(B + 1, B + M + 1), C1[i] = C = std::unique(B + 1, B + M + 1) - B - 1;
for (int j = 1; j <= M; ++j) X[i][j] = std::lower_bound(B + 1, B + C + 1, A[i][j]) - B;
}
for (int j = 1; j <= M; ++j) {
for (int i = 1; i <= N; ++i) B[i] = A[i][j];
std::sort(B + 1, B + N + 1), C2[j] = C = std::unique(B + 1, B + N + 1) - B - 1;
for (int i = 1; i <= N; ++i) Y[i][j] = std::lower_bound(B + 1, B + C + 1, A[i][j]) - B;
}
for (int i = 1; i <= N; ++i, puts(""))
for (int j = 1; j <= M; ++j)
printf("%d ", std::max(X[i][j], Y[i][j]) + std::max(C1[i] - X[i][j], C2[j] - Y[i][j]));
return 0;
}

【B】Camp Schedule

题意简述:

有两个 01 串 \(s\) 和 \(t\),要求重新排列 \(s\) 使得 \(t\) 在重新排列后的 \(s\) 中出现次数尽量多(位置相交的出现也算)。

题解:

贪心匹配 \(t\),随便用什么方法算出 \(t\) 的最长真公共前后缀,我用了 KMP,然后贪心能选就选。

#include <cstdio>
#include <cstring> const int MN = 500005; int N, M, c0, c1;
char s[MN], t[MN];
int p[MN]; int main() {
scanf("%s%s", s + 1, t + 1);
N = strlen(s + 1), M = strlen(t + 1);
for (int i = 1; i <= N; ++i)
if (s[i] == '0') ++c0;
else ++c1;
int k = 0;
for (int i = 2; i <= M; ++i) {
while (k && t[k + 1] != t[i]) k = p[k];
if (t[k + 1] == t[i]) p[i] = ++k;
}
k = 0;
for (int i = 1; i <= N; ++i) {
int r = t[k + 1] - '0';
int q = r ? c1 ? 1 : 0 : c0 ? 0 : 1;
if (q == r) if (++k == M) k = p[M];
--(q ? c1 : c0);
printf("%d", q);
}
return 0;
}

【C】Museums Tour

题意简述:

一个 \(n\) 个点 \(m\) 条边的简单有向图,一周有 \(d\) 天。

每个点都有一个博物馆,给出博物馆 \(i\) 在一周的第 \(j\) 天的开门情况(\(1\le i\le n\),\(1\le j\le d\))。

这周的第 \(1\) 天,你从 \(1\) 号点开始,每次走一条边需要花费 \(1\) 天,如果当前点的博物馆开着,你就可以进去游览。

问在足够长的时间后,你最多可以游览多少个不同的博物馆(一个点的博物馆游览多次只算一次)。

题解:

有两种做法,一种比较简单,一种比较复杂,我写的是复杂的。

考虑 \(\mathrm{f}[i][j]\) 表示一周的第 \(j\) 天在 \(i\) 号点,最终能游览多少个博物馆。

但是图上有环,转移比较困难。于是考虑强连通分量缩点。这样形成一个 DAG。

观察可以得到,一个缩点后的强连通分量,是拥有自己的“周期”的,即若第 \(j\) 天在这个强连通分量中,第 \(j+x\) 天也能在同一个点上,最小的正整数 \(x\) 便是周期。

这个周期一定是 \(d\) 的因数,因为 \(d\) 是所有强连通分量的总周期,周期可以这样计算:

  • 周期 \(\mathrm{period}\) 初始化为 \(d\)。

  • 考虑这个强连通分量的 DFS 树,记录每个点的深度 \(\mathrm{dep}\)。

  • 对于每一条边 \(u\to v\),令 \(\mathrm{period}=\gcd(\mathrm{period},\mathrm{dep}[u]-\mathrm{dep}[v]+1)\)。

关于证明,只能感性理解。

这之后就可以 DP 了,DP 的状态变为 \(\mathrm{f}[i][j]\) 表示 \(i\) 号强连通分量中的时间戳为 \(j\) 的答案。

因为涉及到强连通分量内所有点的时间戳,需要对每个强连通分量设置一个基准时间戳以便互相转化,所幸我们可以用 \(\mathrm{dep}\bmod \mathrm{period}\) 来当作节点的相对时间戳,基准时间戳为 \(0\)。

DP 时需要处理一个 \(\mathrm{cnt}[j]\) 数组表示当强连通分量时间戳为 \(j\) 时这个强连通分量内开启的不同博物馆个数。DP 时要格外注意不同强连通分量中时间戳的转换。

时间复杂度 \(\mathcal{O}((n+m)d)\)。

#include <cstdio>
#include <vector>
#include <algorithm> const int MN = 100005, MD = 55; int N, M, D;
std::vector<int> G[MN], T[MN];
char s[MD]; int w[MN][MD]; int dfn[MN], low[MN], stk[MN], instk[MN], tp, dfc;
int dep[MN], bel[MN], per[MN], tic[MN], itic[MN], bcnt;
void Tarjan(int u) {
low[u] = dfn[u] = ++dfc;
stk[++tp] = u, instk[u] = 1;
for (auto v : G[u]) {
if (!dfn[v]) {
dep[v] = dep[u] + 1, Tarjan(v);
low[u] = std::min(low[u], low[v]);
}
else if (instk[v]) low[u] = std::min(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
++bcnt;
for (int x = 0; x != u; --tp) {
x = stk[tp];
bel[x] = bcnt;
T[bcnt].push_back(x);
instk[x] = 0;
}
}
} int f[MN][MD]; int main() {
scanf("%d%d%d", &N, &M, &D);
for (int i = 1; i <= M; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
}
for (int i = 1; i <= N; ++i)
if (!dfn[i]) dep[i] = 1, Tarjan(i);
for (int i = 1; i <= bcnt; ++i) per[i] = D;
for (int u = 1; u <= N; ++u)
for (auto v : G[u]) if (bel[u] == bel[v])
per[bel[u]] = std::__gcd(per[bel[u]], std::abs(dep[u] - dep[v] + 1));
for (int i = 1; i <= N; ++i)
tic[i] = dep[i] % per[bel[i]], itic[i] = tic[i] ? per[bel[i]] - tic[i] : 0;
for (int i = 1; i <= N; ++i) {
scanf("%s", s);
for (int j = 0; j < D; ++j) w[i][j] = s[j] - '0';
}
for (int id = 1; id <= bcnt; ++id) {
int pr = per[id];
static int cnt[MD];
for (int j = 0; j < pr; ++j) cnt[j] = 0;
for (int j = 0; j < pr; ++j) {
for (auto u : T[id]) for (int k = 0; k < D; k += pr)
if (w[u][(k + tic[u] + j) % D]) { ++cnt[j]; break; }
}
for (int j = 0; j < pr; ++j) {
f[id][j] = cnt[j];
for (auto u : T[id]) {
int ticv = (j + tic[u] + 1) % pr;
for (auto v : G[u]) if (bel[v] != id) {
for (int k = 0; k < D; k += pr)
f[id][j] = std::max(f[id][j], cnt[j] + f[bel[v]][(itic[v] + k + ticv) % per[bel[v]]]);
}
}
}
}
printf("%d\n", f[bel[1]][itic[1]]);
return 0;
} // 19-03-10 00:18 ~ 19-03-10 00:54

而相对简单的做法是,将每个点在一周的每天拆成不同的点。这样有 \(n\times d\) 个点,每个点记作 \(\langle u,j\rangle\) 即 \(u\) 号点在第 \(j\) 天拆出的点(\(1\le u\le n\),\(0\le j<d\))。每个点的权值为 \(0\) 或 \(1\),取决于当天此点的博物馆是否开启。

对于一条边 \(u\to v\),将其拆成 \(d\) 条边,连接 \(\langle u,j\rangle\) 和 \(\langle v,(j+1)\bmod d\rangle\)。

这样再强连通分量缩点后,形成一个 DAG。每个缩点后的强连通分量权值为这个强连通分量中开启的不同博物馆个数,直接求最长路即可。

为什么不会重复统计?考虑重复统计的情况,即存在一条路径使得 \(\langle u,j_1\rangle\) 可以到达 \(\langle u,j_2\rangle\) 但不在同一个强连通分量中,这是不可能的,因为单独看这条路径经过的顶点编号序列,沿着这个序列再重复走 \(d-1\) 遍一定能回到 \(\langle u,j_1\rangle\)。这意味着如果同一顶点拆出的两点弱连通,它们必然也强连通,所以不需要担心重复统计的情况。

代码就不给了,比较简单。不过这个做法似乎空间翻好几番,要小心处理。


【D】Cooperative Game

题意简述:

这是一道交互题。

有一张 \(t+c\)(\(1\le t,c\),\(t+c\le 1000\))个点的有向图,每个点只有一条出边。这张图由一个长度为 \(t\) 的链和一个长度为 \(c\) 的环构成。

即从起点(链的顶端)走 \(t-1\) 条边即可访问到链的尾端,从链的尾端再走 \(1\) 条边即可进入环中,记环上的这个点(从链进入的第一个点)为 \(\mathrm{finish}\)。

你在起点(链的顶端)有 \(10\) 枚棋子,每次你可以令一部分棋子走一条边,然后交互库会告诉你哪些棋子在同一个节点内,哪些棋子在不同节点内。

你需要在不超过 \(3(t+c)\) 次操作后让所有棋子到达 \(\mathrm{finish}\) 并结束程序。

题解:

思路非常巧妙的一题。其实只需要 \(3\) 枚棋子即可完成任务。

考虑先移动 \(2\) 枚棋子,类似 Floyd 判圈算法,以及 Pollad-rho 算法的判圈过程,我们令一枚棋子速度为 \(2\),另一枚速度为 \(1\)。

它们一定会在环上相遇,并且相遇时,较慢的棋子还没有走完一整个环,令相遇的点为 \(\mathrm{meet}\)。

假设从 \(\mathrm{finish}\) 开始,走 \(x\) 条边第一次到达 \(\mathrm{meet}\),较慢的棋子走了 \(\mathrm{slow}\) 步,较快的棋子走了 \(\mathrm{fast}\) 步(显然 \(\mathrm{fast}=2\times\mathrm{slow}\)):

有 \(\mathrm{slow}=t+x\) 和 \(2\times\mathrm{slow}=\mathrm{fast}=t+k\times c+x\)(\(k\in\mathbb{Z}^+\)),整理得:

\(2(t+x)=t+k\times c+x\),即 \(t+x=k\times c\),即 \(t\equiv c-x\pmod{c}\)。

\(c-x\) 即为 \(\mathrm{meet}\) 在环上剩余的步数,即从 \(\mathrm{meet}\) 走 \(c-x\) 步又到达 \(\mathrm{finish}\)。

所以有剩余的步数加上若干倍的 \(c\) 等于 \(t\)。也就是说,如果这时开始,我们每次让所有棋子同时走一条边,最终在 \(t\) 步之后就都会到达 \(\mathrm{finish}\),这正是我们要求的。

于是有了下面的代码:

#include <cstdio>

int t;
inline void gett() {
fflush(stdout);
scanf("%d", &t);
for (int x = t; x; --x) scanf("%*s");
} int main() {
do {
puts("next 0 1"), gett();
puts("next 0"), gett();
} while (t == 3);
do {
puts("next 0 1 2 3 4 5 6 7 8 9"), gett();
} while (t == 2);
puts("done");
return 0;
}

【E】Train Car Selection

CodeForces Contest #1137: Round #545 (Div. 1)的更多相关文章

  1. CodeForces Contest #1114: Round #538 (Div. 2)

    比赛传送门:CF #1114. 比赛记录:点我. 又 FST 了. [A]Got Any Grapes? 题意简述: 有三个人,第一个人需要吃绿色葡萄至少 \(a\) 个,第二个人需要吃绿色和紫色葡萄 ...

  2. Codeforces Round #545 (Div. 1) 简要题解

    这里没有翻译 Codeforces Round #545 (Div. 1) T1 对于每行每列分别离散化,求出大于这个位置的数字的个数即可. # include <bits/stdc++.h&g ...

  3. Codeforces Round #545 (Div. 2) D 贪心 + kmp

    https://codeforces.com/contest/1138/problem/D 题意 两个01串s和t,s中字符能相互交换,问最多能得到多少个(可交叉)的t 题解 即将s中的01塞进t中, ...

  4. Codeforces Round #545 (Div. 2) D

    链接:http://codeforces.com/contest/1138/problem/D 啊啊啊啊啊啊,自闭啊,比赛的时候判断条件 if(s1[i-1]=='0') aa++;写成了 if(s1 ...

  5. Codeforces Round #545 (Div. 2)(D. Camp Schedule)

    题目链接:http://codeforces.com/contest/1138/problem/D 题目大意:给你两个字符串s1和s2(只包含0和1),对于s1中,你可以调换任意两个字符的位置.问你最 ...

  6. Codeforces Round #545 (Div. 2)(B. Circus)

    题目链接:http://codeforces.com/contest/1138/problem/B 题目大意:贼绕口的题目,就是给你两个字符串s1,s2,然后每一个人代表一列,第一列代表技能一每个人是 ...

  7. Codeforces Round #545 (Div. 2) E 强连通块 + dag上求最大路径 + 将状态看成点建图

    https://codeforces.com/contest/1138/problem/E 题意 有n个城市(1e5),有m条单向边(1e5),每一周有d天(50),对于每个城市假如在某一天为1表示这 ...

  8. Codeforces Round #545 (Div. 2) 交互 + 推公式

    https://codeforces.com/contest/1138/problem/F 题意 有一条长为t的链,一个长为c的环,定义终点为链和环相连环上的第一个点,现在有10个人在起点,你每次可以 ...

  9. Codeforces Round #545 (Div. 1) Solution

    人生第一场Div. 1 结果因为想D想太久不晓得Floyd判环法.C不会拆点.E想了个奇奇怪怪的set+堆+一堆乱七八糟的标记的贼难写的做法滚粗了qwq靠手速上分qwqqq A. Skyscraper ...

随机推荐

  1. 自学工业控制网络之路2.2-PROFINET

    返回 自学工业控制网络之路 自学工业控制网络之路2.2-PROFINET PROFINET由PROFIBUS国际组织(PROFIBUS International,PI)推出,是新一代基于工业以太网技 ...

  2. 自学Aruba3.2-Aruba配置架构-Virtual AP配置要点

    点击返回:自学Aruba之路 自学Aruba3.2-Aruba配置架构-Virtual AP配置要点  1. AP.AP-Group和Virtual-AP的关系 解析列举:      AP1.AP3, ...

  3. [luogu1912][bzoj4196][NOI2015]软件管理器

    题解 树剖模板题,每次改变是\(1\)或者是\(0\),区间求和和区间修改就可了. ac代码 # include <cstdio> # include <cstring> # ...

  4. linux 分区、目录及用途

    主要分区: 目录 建议大小 格式 描述 / 10G-20G ext4 根目录 swap <2048M swap 交换空间 /boot 200M左右 ext4 Linux的内核及引导系统程序所需要 ...

  5. 洛谷P3233 世界树

    题意:给定树上k个关键点,每个点属于离他最近,然后编号最小的关键点.求每个关键点管辖多少点. 解:虚树 + DP. 虚树不解释.主要是DP.用二元组存虚树上每个点的归属和距离.这一部分是二次扫描与换根 ...

  6. A1080. Graduate Admission

    It is said that in 2013, there were about 100 graduate schools ready to proceed over 40,000 applicat ...

  7. 误删除 linux 系统文件了?这个方法教你解决

    转载于互联网并适当的修改 误删除linux系统文件了?不用急,本文将给你一个恢复linux文件的方法,让你轻松应对运维中的各风险问题.方法总比问题多~ 说在前面的话 针对日常维护操作,难免会出现文件误 ...

  8. CodeForces912E 折半+二分+双指针

    http://codeforces.com/problemset/problem/912/E 题意·n个质数,问因子中只包含这其中几个质数的第k大的数是多少 最显然的想法是暴力搜预处理出所有的小于1e ...

  9. python influxdb

    Git:https://github.com/influxdata/influxdb-python 帮助文档:http://influxdb-python.readthedocs.io/en/late ...

  10. 【Python】统计个人新浪微博词频并给出相应的柱状图

    Python爬虫视频教程零基础小白到scrapy爬虫高手-轻松入门 https://item.taobao.com/item.htm?spm=a1z38n.10677092.0.0.482434a6E ...