【题解】联合省选 2021 题解
A. D1T1 卡牌游戏
可以枚举一个 \(p\),然后限制最小值 \(\geq p\),接着找到最小的 \(q\) 使得限制最大值 \(\leq q\) 是合法的。
那么这个限制就相当于:
- 对于 \(a_i < p\),必须要满足 \(p \leq b_i \leq q\),记这类点数为 \(c_1\)。
- 对于 \(a_i > q\),必须要满足 \(p \leq b_i \leq q\),记这类点数为 \(c_2\)。
- \(c_1+c_2 \leq m\)。
\(p\) 增加的时候,最优 \(q\) 必然是不下降的。双指针移动一下,注意实现。时间复杂度 \(\mathcal O(n \log n)\) 或 \(\mathcal O(n)\)。
#include <bits/stdc++.h>
using std::cerr;
template <class T>
inline void read(T &x) {
static char ch;
while (!isdigit(ch = getchar()));
x = ch - '0';
while (isdigit(ch = getchar()))
x = x * 10 + ch - '0';
}
template <class T>
inline void tense(T &x, const T &y) {
if (x > y) x = y;
}
template <class T>
inline void relax(T &x, const T &y) {
if (x < y) x = y;
}
const int MaxN = 1e6 + 5;
const int MaxM = 2e6 + 5;
const int INF = 0x3f3f3f3f;
int n, m;
int a[MaxN], b[MaxN];
int preMax[MaxN], preMin[MaxN];
int sufMax[MaxN], sufMin[MaxN];
int nV, val[MaxM];
int main() {
freopen("card.in", "r", stdin);
freopen("card.out", "w", stdout);
read(n), read(m);
for (int i = 1; i <= n; ++i) read(a[i]), val[++nV] = a[i];
for (int i = 1; i <= n; ++i) read(b[i]), val[++nV] = b[i];
std::sort(val + 1, val + nV + 1);
preMax[0] = sufMax[n + 1] = 0;
preMin[0] = sufMin[n + 1] = INF;
for (int i = 1; i <= n; ++i) {
a[i] = std::lower_bound(val + 1, val + nV + 1, a[i]) - val;
b[i] = std::lower_bound(val + 1, val + nV + 1, b[i]) - val;
relax(preMax[i] = preMax[i - 1], b[i]);
tense(preMin[i] = preMin[i - 1], b[i]);
}
for (int i = n; i >= 1; --i) {
relax(sufMax[i] = sufMax[i + 1], b[i]);
tense(sufMin[i] = sufMin[i + 1], b[i]);
}
int res = INF;
for (int l = 1, r = 0, c1 = 0, c2 = n; l <= nV; ++l) {
while (c1 < n && a[c1 + 1] < l) ++c1;
if (preMin[c1] < l || c1 > m) break;
while (
(c1 + c2 > m)
|| (preMax[c1] > r)
|| (sufMin[n - c2 + 1] < l || sufMax[n - c2 + 1] > r)
) {
++r;
while (c2 > 0 && a[n - c2 + 1] <= r) --c2;
}
tense(res, val[r] - val[l]);
}
printf("%d\n", res);
return 0;
}
B. D1T2 矩阵游戏
思路一
首先,如果确定了第一行和第一列,那么整个矩阵就可以确定了。
一通观察+归纳证明,不难发现
其中 \(K_{i,j}\) 是可以通过 \(b\) 矩阵唯一确定的一个常数。
不难发现,需要处理的就是 \(a_{i,j} \in [0,10^6]\) 的限制,就相当于:
这个形式对我们很有启发,两边同时乘上 \((-1)^{i+j+1}\),尝试将每一项整理一下
把 \(a_{1,1}\) 随便和 \((-1)^{i}a_{i,1}\) 放到一起,这下就可以把 \(i,j\) 的变量分成两部分了。不难写成差分约束的形式:
这下就可以做一个 \(\mathcal O(n+m)\) 个点 \(\mathcal O(nm)\) 条边的差分约束了。
时间复杂度 \(\mathcal O(T(n+m)nm)\),SPFA 跑不满。
思路二
我看了看别人的题解,学到了一个明显更简单的思路。
将第一行、第一列都赋成 \(0\),先得到一个满足矩阵 \(b\),但是不一定满足范围的解,记为 \(p\)。
利用若干次以下两类操作,我们可以用矩阵 \(p\) 构造出任意一组满足矩阵 \(b\) 的解:
- 操作一:选择其中一行,依次将每个数 \(+1,-1,+1,\cdots\)(权值记为 \(+1\))或 \(-1,+1,-1,\cdots\)(权值记为 \(-1\))
- 操作二:选择其中一列,依次将每个数 \(+1,-1,+1,\cdots\)(权值记为 \(+1\))或 \(-1,+1,-1,\cdots\)(权值记为 \(-1\))
因为两种操作完全可以将第一行、第一列修改成任意情况,所以整个矩阵 \(a\) 的所有可能情况都可以达到。
记第 \(i\) 行进行的操作一总权值为 \(c_i\),第 \(j\) 列进行的操作二总权值为 \(d_i\)。那么
同「思路一」操作一下,就转化成差分约束了,这里就不赘述了。
#include <bits/stdc++.h>
#define foredge(u) for (int e = adj[u], v; v = to[e], e; e = nxt[e])
using std::cerr;
typedef long long s64;
template <class T>
inline void read(T &x) {
static char ch;
while (!isdigit(ch = getchar()));
x = ch - '0';
while (isdigit(ch = getchar()))
x = x * 10 + ch - '0';
}
template <class T>
inline void putint(T x) {
static char buf[15], *tail = buf;
if (!x) putchar('0');
else {
if (x < 0) x = ~x + 1, putchar('-');
for (; x; x /= 10) *++tail = x % 10 + '0';
for (; tail != buf; --tail) putchar(*tail);
}
}
template <class T>
inline bool tense(T &x, const T &y) {
return x > y ? x = y, true : false;
}
template <class T>
inline bool relax(T &x, const T &y) {
return x < y ? x = y, true : false;
}
const int MaxN = 3e2 + 5;
const int MaxV = 7e2 + 5;
const int MaxE = MaxN * MaxN * 2 + MaxV * 2;
int n, m, nV, lim = 1000000;
s64 b[MaxN][MaxN], dis[MaxV];
bool flgCir;
int ect, nxt[MaxE], to[MaxE], weight[MaxE];
int adj[MaxV];
inline void init() {
ect = 0;
for (int i = 1; i <= nV; ++i) adj[i] = 0;
}
inline void addEdge(int u, int v, int w) {
nxt[++ect] = adj[u];
adj[u] = ect;
to[ect] = v;
weight[ect] = w;
}
inline void addSeg(int u, int v, s64 l, s64 r) {
relax(l, -3LL * lim), tense(r, 3LL * lim);
if (l > r) flgCir = true;
addEdge(u, v, (int)r);
addEdge(v, u, -(int)l);
}
void SPFA() {
static int que[MaxV * MaxV], cnt[MaxV], qr;
static bool inq[MaxV];
for (int i = 1; i <= nV; ++i) {
cnt[i] = 0; //!!!
inq[i] = false;
dis[i] = 1LL << 60;
}
inq[nV] = true;
dis[que[qr = 1] = nV] = 0;
for (int i = 1; i <= qr; ++i) {
int u = que[i];
inq[u] = false;
foredge(u) if (tense(dis[v], dis[u] + weight[e])) {
if ((cnt[v] = cnt[u] + 1) >= nV) {
flgCir = true;
return;
}
if (!inq[v]) inq[que[++qr] = v] = true;
}
}
}
void work() {
init();
flgCir = false;
read(n), read(m), nV = n + m;
addSeg(nV, 1, 0, lim);
for (int i = 2; i <= n; ++i) {
if (i & 1)
addSeg(1, i, -lim, 0);
else
addSeg(1, i, 0, lim);
}
for (int i = 2; i <= m; ++i) {
int p = i + n - 1;
if (i & 1)
addSeg(nV, p, 0, lim);
else
addSeg(nV, p, -lim, 0);
}
for (int i = 2; i <= n; ++i)
for (int j = 2; j <= m; ++j) {
read(b[i][j]);
b[i][j] -= b[i - 1][j];
b[i][j] -= b[i][j - 1];
b[i][j] -= b[i - 1][j - 1];
if ((i + j + 1) & 1)
addSeg(j + n - 1, i, -lim + b[i][j], +b[i][j]);
else
addSeg(j + n - 1, i, -b[i][j], -b[i][j] + lim);
}
if (flgCir) return (void)(puts("NO"));
SPFA();
if (flgCir) return (void)(puts("NO"));
puts("YES");
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) {
int res =
b[i][j]
+ ((i + j + 1) & 1 ? -1 : 1) * dis[i]
+ ((i + j) & 1 ? -1 : 1) * dis[j + n - 1];
if (i == 1) {
res = (j & 1 ? 1 : -1) * dis[j == 1 ? 1 : j + n - 1];
} else if (j == 1) {
res = (i & 1 ? -1 : 1) * (dis[i] - dis[1]);
}
putint(res), putchar(" \n"[j == m]);
}
}
int main() {
freopen("matrix.in", "r", stdin);
freopen("matrix.out", "w", stdout);
int T;
read(T);
while (T--) work();
return 0;
}
#include <bits/stdc++.h>
#define foredge(u) for (int e = adj[u], v; v = to[e], e; e = nxt[e])
using std::cerr;
typedef long long s64;
template <class T>
inline void read(T &x) {
static char ch;
while (!isdigit(ch = getchar()));
x = ch - '0';
while (isdigit(ch = getchar()))
x = x * 10 + ch - '0';
}
template <class T>
inline void putint(T x) {
static char buf[15], *tail = buf;
if (!x) putchar('0');
else {
if (x < 0) x = ~x + 1, putchar('-');
for (; x; x /= 10) *++tail = x % 10 + '0';
for (; tail != buf; --tail) putchar(*tail);
}
}
template <class T>
inline bool tense(T &x, const T &y) {
return x > y ? x = y, true : false;
}
template <class T>
inline bool relax(T &x, const T &y) {
return x < y ? x = y, true : false;
}
const int MaxN = 3e2 + 5;
const int MaxV = 7e2 + 5;
const int MaxE = MaxN * MaxN * 2 + MaxV * 2;
const int INF = 0x3f3f3f3f;
int n, m, nV, lim = 1000000;
s64 b[MaxN][MaxN], dis[MaxV];
bool flgCir;
int ect, nxt[MaxE], to[MaxE], weight[MaxE];
int adj[MaxV];
int g[MaxV][MaxV];
inline void init() {
ect = 0;
for (int i = 1; i <= nV; ++i) adj[i] = 0;
for (int i = 1; i <= nV; ++i)
for (int j = 1; j <= nV; ++j) {
g[i][j] = INF;
}
}
inline void addEdge(int u, int v, int w) {
tense(g[u][v], w);
}
inline void addSeg(int u, int v, s64 l, s64 r) {
relax(l, -3LL * lim), tense(r, 3LL * lim);
if (l > r) flgCir = true;
addEdge(u, v, (int)r);
addEdge(v, u, -(int)l);
}
void SPFA() {
static int que[MaxV * MaxV], cnt[MaxV], qr;
static bool inq[MaxV];
for (int i = 1; i <= nV; ++i) {
cnt[i] = 0; //!!!
inq[i] = false;
dis[i] = 1LL << 60;
}
inq[nV] = true;
dis[que[qr = 1] = nV] = 0;
for (int i = 1; i <= qr; ++i) {
int u = que[i];
inq[u] = false;
for (int v = 1; v <= nV; ++v) if (tense(dis[v], dis[u] + g[u][v])) {
if ((cnt[v] = cnt[u] + 1) >= nV) {
flgCir = true;
return;
}
if (!inq[v]) inq[que[++qr] = v] = true;
}
}
}
void work() {
read(n), read(m), nV = n + m;
init();
flgCir = false;
addSeg(nV, 1, 0, lim);
for (int i = 2; i <= n; ++i) {
if (i & 1)
addSeg(1, i, -lim, 0);
else
addSeg(1, i, 0, lim);
}
for (int i = 2; i <= m; ++i) {
int p = i + n - 1;
if (i & 1)
addSeg(nV, p, 0, lim);
else
addSeg(nV, p, -lim, 0);
}
for (int i = 2; i <= n; ++i)
for (int j = 2; j <= m; ++j) {
read(b[i][j]);
b[i][j] -= b[i - 1][j];
b[i][j] -= b[i][j - 1];
b[i][j] -= b[i - 1][j - 1];
if ((i + j + 1) & 1)
addSeg(j + n - 1, i, -lim + b[i][j], +b[i][j]);
else
addSeg(j + n - 1, i, -b[i][j], -b[i][j] + lim);
}
if (flgCir) return (void)(puts("NO"));
SPFA();
if (flgCir) return (void)(puts("NO"));
puts("YES");
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) {
int res =
b[i][j]
+ ((i + j + 1) & 1 ? -1 : 1) * dis[i]
+ ((i + j) & 1 ? -1 : 1) * dis[j + n - 1];
if (i == 1) {
res = (j & 1 ? 1 : -1) * dis[j == 1 ? 1 : j + n - 1];
} else if (j == 1) {
res = (i & 1 ? -1 : 1) * (dis[i] - dis[1]);
}
putint(res), putchar(" \n"[j == m]);
}
}
int main() {
freopen("matrix.in", "r", stdin);
freopen("matrix.out", "w", stdout);
int T;
read(T);
while (T--) work();
return 0;
}
C. D1T3 图函数
首先不难得到一个结论:在计算 \(f(u,G)\) 的过程中,如果我们按顺序枚举到一个点 \(v\),那么在 \(v\) 前面的点 \(w\) 如果没删掉,删掉 \(w\) 也不会改变我们的判断结果。
这是因为,我们在判断 \(v\) 是否会在 \(f(u,G)\) 中产生贡献的时候,实际上是判断 \(u,v\) 在当前的图上是否属于同一个强连通分量。而之前没有删掉的点 \(w\) 和 \(u\) 一定就不在一个强连通分量中,删掉它不影响我们的判断。
于是 \(f(u,G)= \sum_{v} [\text{删掉 } 1 \sim v-1 \text{ 的点后 } u,v \text{ 仍然连通}]\)。
记命题 \(p(i,u,v)\) 表示 \(u,v\) 之间存在一条路径,使得这条路径经过的点编号都 \(\geq i\)。
显然使得 \(p(i,u,v)\) 成立的图一定是 \(G_0\sim G_m\) 的一段前缀。如果能对于每个点对 \((u,v)\) 找到最大的 \(e\),使得 \(p(v,u,v)\land p(v,v,u)\) 在 \(G_0\sim G_{e-1}\) 都成立,那么就可以将其贡献到 \(h(G_0)\sim h(G_{e-1})\) 中。
现在的问题就是如何找到这个 \(e\),下面一一介绍。
思路一
注意到满足 \(p(u,u,v)\land p(u,v,u)\) 这个条件的最大的 \(e\) 可以看成,在正图 \(G\) 和反图 \(G^R\) 上 \(u\) 经过 \(\geq u\) 的点走到 \(v\) 的所有路径中,分别找到路径最小边权(边的编号)的最大值。
一个想法就是枚举起点 \(u\),然后删去 \(< u\) 的点,以它为起点跑 Dijkstra。时间复杂度 \(\mathcal O(nm\log m)\)。
实际上限制只能经过编号 \(\geq u\) 的点,可以通过 Floyd 枚举第一维来实现,用 Floyd 求解即可。时间复杂度 \(\mathcal O(n^3)\)。
code \(O(nm \log m)\)
#include <bits/stdc++.h>
#define foredge(u, G) for (int e = G.adj[u], v; v = G.to[e], e; e = G.nxt[e])
using std::min;
using std::cerr;
using std::pair;
using std::make_pair;
typedef std::vector<int> vi;
typedef std::pair<int, int> pii;
template <class T>
inline void read(T &x) {
static char ch;
while (!isdigit(ch = getchar()));
x = ch - '0';
while (isdigit(ch = getchar()))
x = x * 10 + ch - '0';
}
template <class T>
inline bool relax(T &x, const T &y) {
return x < y ? x = y, true : false;
}
const int INF = 0x3f3f3f3f;
const int MaxN = 1000 + 5;
const int MaxE = 4e5 + 5;
struct graph {
int ect, adj[MaxN];
int nxt[MaxE], to[MaxE], weight[MaxE];
inline void addEdge(int u, int v, int w) {
nxt[++ect] = adj[u];
adj[u] = ect;
to[ect] = v;
weight[ect] = w;
}
}G1, G2;
int n, m;
int ans[MaxE];
void Dijkstra(int st, int *dis, graph &G) {
static std::priority_queue<pii> heap;
static bool vis[MaxN];
for (int i = 1; i <= n; ++i) {
dis[i] = 0;
vis[i] = false;
}
while (!heap.empty()) heap.pop();
heap.push(make_pair(dis[st] = m + 1, st));
while (!heap.empty()) {
int u = heap.top().second;
heap.pop();
if (vis[u]) continue;
vis[u] = true;
foredge(u, G)
if (v >= st && !vis[v] && relax(dis[v], min(dis[u], G.weight[e])))
heap.push(make_pair(dis[v], v));
}
}
int main() {
freopen("graph.in", "r", stdin);
freopen("graph.out", "w", stdout);
read(n), read(m);
for (int i = 1; i <= m; ++i) {
int u, v;
read(u), read(v);
G1.addEdge(u, v, i);
G2.addEdge(v, u, i);
}
for (int st = 1; st <= n; ++st) {
static int dis1[MaxN], dis2[MaxN];
Dijkstra(st, dis1, G1);
Dijkstra(st, dis2, G2);
for (int i = st; i <= n; ++i)
++ans[min(dis1[i], dis2[i])];
}
for (int i = m; i >= 1; --i)
ans[i] += ans[i + 1];
for (int i = 1; i <= m + 1; ++i) {
printf("%d%c", ans[i], " \n"[i == m + 1]);
}
return 0;
}
code \(\mathcal O(n^3)\)
#include <bits/stdc++.h>
#define foredge(u) for (halfEdge *e = adj[u]; e; e = e->next)
using std::min;
using std::cerr;
using std::pair;
using std::vector;
using std::make_pair;
typedef std::vector<int> vi;
typedef std::pair<int, int> pii;
template <class T>
inline void read(T &x) {
static char ch;
while (!isdigit(ch = getchar()));
x = ch - '0';
while (isdigit(ch = getchar()))
x = x * 10 + ch - '0';
}
template <class T>
inline void relax(T &x, const T &y) {
x < y ? x = y : 0;
}
const int INF = 0x3f3f3f3f;
const int MaxN = 1000 + 5;
const int MaxE = 4e5 + 5;
int n, m;
int ans[MaxE];
int f[MaxN][MaxN];
int main() {
freopen("graph.in", "r", stdin);
freopen("graph.out", "w", stdout);
read(n), read(m);
for (int i = 1; i <= m; ++i) {
int u, v;
read(u), read(v);
relax(f[u][v], i);
}
for (int k = n; k >= 1; --k) {
relax(f[k][k], m + 1);
for (int u = 1; u <= n; ++u) if (f[u][k])
for (int v = 1, lim = u > k ? k - 1 : n; v <= lim; ++v)
relax(f[u][v], min(f[u][k], f[k][v]));
for (int v = k; v <= n; ++v)
++ans[min(f[v][k], f[k][v])];
}
for (int i = m; i >= 1; --i)
ans[i] += ans[i + 1];
for (int i = 1; i <= m + 1; ++i) {
printf("%d%c", ans[i], " \n"[i == m + 1]);
}
return 0;
}
思路二
反过来考虑,我们倒序加边,看看每个点对在加入哪条边的时候恰好连通。
假设 \(f(u,v)\) 表示在当前加入的边集中,\(u\) 能否通过 \(\geq u\) 的点到达 \(v\)。
倒序枚举到边 \((x,y)\) 的时候,再枚举点对的起点 \(u(u\leq y)\):
- 如果 \(f(u,x)=1\) 且 \(f(u,y)=0\)
- 那么就从 \(y\) 点出发 DFS/BFS
- 将所有访问到的 \(v\geq u\) 且 \(f(u,v)=0\) 的点更新。
注意到在上述过程中,一个起点 \(u\) 中每个点 \(v\) 只会被访问至多一次,因此边也至多访问一次,时间复杂度就是 \(\mathcal O(nm+n^2)\)。
实际上,有些枚举是冗余的,因为在枚举过程中实际上用到的点对并不多:
- 对于枚举的边 \((x,y)\),如果枚举的 \(u\) 满足 \(f(u,x)=1\) 且 \(f(u,y)=0\),那么这样的 \((u,y)\) 至多有 \(\mathcal O(n^2)\) 对。
- 对于 DFS/BFS 过程中访问到的点 \(v\),实际上在枚举的每个起点 \(u\) 中也只会至多访问一次,枚举出边的过程是冗余的。
用 bitset 的 _Find_first 和 _Find_next,我们就可以很好地解决冗余枚举的问题。只需要对每个点 \(v\) 多记一个 \(g(v,u)=f(u,v)\) 即可。
总的时间复杂度就可以优化到 \(\mathcal O(\frac{n^3+nm}{\omega})\),这个应该是比较正确的复杂度,实际效率也是这几种算法中最优的。
code \(O(nm)\)
#include <bits/stdc++.h>
#define foredge(u) for (halfEdge *e = adj[u]; e; e = e->next)
using std::min;
using std::cerr;
using std::pair;
using std::make_pair;
typedef std::vector<int> vi;
typedef std::pair<int, int> pii;
template <class T>
inline void read(T &x) {
static char ch;
while (!isdigit(ch = getchar()));
x = ch - '0';
while (isdigit(ch = getchar()))
x = x * 10 + ch - '0';
}
template <class T>
inline bool relax(T &x, const T &y) {
return x < y ? x = y, true : false;
}
const int INF = 0x3f3f3f3f;
const int MaxN = 1000 + 5;
const int MaxE = 4e5 + 5;
struct edge {
int u, v;
}e1[MaxE], e2[MaxE];
struct halfEdge {
int v;
halfEdge *next;
}adjPool[MaxE], *adjTail = adjPool, *adj[MaxN];
int n, m;
int ans[MaxE];
inline void clear() {
adjTail = adjPool;
for (int i = 1; i <= n; ++i) {
adj[i] = NULL;
}
}
inline void addEdge(int u, int v) {
adjTail->v = v;
adjTail->next = adj[u];
adj[u] = adjTail++;
}
void bfs(int u, int *dis, int disval) {
static int que[MaxN], qr;
dis[que[qr = 1] = u] = disval;
for (int i = 1; i <= qr; ++i) {
int u = que[i];
foredge(u) if (!dis[e->v])
dis[que[++qr] = e->v] = disval;
}
}
void solve(int st, int *dis, edge *e) {
for (int i = 1; i <= n; ++i) {
dis[i] = 0;
}
clear();
dis[st] = m + 1;
for (int i = m; i >= 1; --i) {
int u = e[i].u, v = e[i].v;
if (u >= st && v >= st) {
if (!dis[u] && !dis[v])
addEdge(u, v);
else if (dis[u] && !dis[v])
bfs(v, dis, i);
}
}
}
int main() {
freopen("graph.in", "r", stdin);
freopen("graph.out", "w", stdout);
read(n), read(m);
for (int i = 1; i <= m; ++i) {
int u, v;
read(u), read(v);
e1[i] = (edge){u, v};
e2[i] = (edge){v, u};
}
for (int st = 1; st <= n; ++st) {
static int dis1[MaxN], dis2[MaxN];
solve(st, dis1, e1);
solve(st, dis2, e2);
for (int i = st; i <= n; ++i)
++ans[min(dis1[i], dis2[i])];
}
for (int i = m; i >= 1; --i)
ans[i] += ans[i + 1];
for (int i = 1; i <= m + 1; ++i) {
printf("%d%c", ans[i], " \n"[i == m + 1]);
}
return 0;
}
code \(\mathcal O(\frac{n^3+nm}{\omega})\)
#include <bits/stdc++.h>
#define foredge(u) for (halfEdge *e = adj[u]; e; e = e->next)
using std::min;
using std::cerr;
using std::pair;
using std::make_pair;
typedef std::vector<int> vi;
typedef std::pair<int, int> pii;
template <class T>
inline void read(T &x) {
static char ch;
while (!isdigit(ch = getchar()));
x = ch - '0';
while (isdigit(ch = getchar()))
x = x * 10 + ch - '0';
}
template <class T>
inline bool relax(T &x, const T &y) {
return x < y ? x = y, true : false;
}
const int INF = 0x3f3f3f3f;
const int MaxN = 1000 + 5;
const int MaxE = 4e5 + 5;
typedef std::bitset<MaxN> bst;
struct edge {
int u, v;
}e[2][MaxE];
int n, m;
int ans[MaxE];
int dis[2][MaxN][MaxN];
bst vis[MaxN], bel[MaxN], adj[MaxN];
void fillVis(int st, int src, int disVal, int opt) {
static int que[MaxN], qr;
vis[src][que[qr = 1] = st] = 1;
bel[st][src] = 1;
dis[opt][src][st] = disVal;
for (int i = 1; i <= qr; ++i) {
int u = que[i];
bst nxt = (~vis[src]) & adj[u];
for (int v = nxt._Find_next(src); v <= n; v = nxt._Find_next(v)) {
vis[src][que[++qr] = v] = 1;
bel[v][src] = 1;
dis[opt][src][v] = disVal;
}
}
}
void solve(int opt) {
for (int i = 1; i <= n; ++i) {
vis[i].reset();
bel[i].reset();
adj[i].reset();
bel[i][i] = vis[i][i] = 1;
dis[opt][i][i] = m + 1;
}
for (int ie = m; ie >= 1; --ie) {
int s = e[opt][ie].u;
int t = e[opt][ie].v;
adj[s][t] = 1;
bst st = bel[s] & (~bel[t]);
for (int u = st._Find_first(); u <= t; u = st._Find_next(u))
fillVis(t, u, ie, opt);
}
}
int main() {
freopen("graph.in", "r", stdin);
freopen("graph.out", "w", stdout);
read(n), read(m);
for (int i = 1; i <= m; ++i) {
int u, v;
read(u), read(v);
e[0][i] = (edge){u, v};
e[1][i] = (edge){v, u};
}
solve(0);
solve(1);
for (int i = 1; i <= n; ++i)
for (int j = i; j <= n; ++j)
++ans[min(dis[0][i][j], dis[1][i][j])];
for (int i = m; i >= 1; --i)
ans[i] += ans[i + 1];
for (int i = 1; i <= m + 1; ++i) {
printf("%d%c", ans[i], " \n"[i == m + 1]);
}
return 0;
}
D. D2T1 宝石
算法一
这个是我自己的思路。
首先注意题目的条件:「\(P_1,P_2,\cdots,P_c\) 互不相等」。那么每个点就唯一对应排列中的一个编号,每个点的下一个和上一个匹配元素也很容易找到。
根据这一点,可以设计出一个寻找最长匹配子序列的思路。
首先,我们求出:
- \(\mathrm{par}_1(x)\) 表示能在 \(x\) 的祖先中找到的,点权恰好等于 \(x\) 在排列中对应的下一个元素,且深度最深的点。
- \(\mathrm{par}_2(x)\) 表示能在 \(x\) 的祖先中找到的,点权恰好等于 \(x\) 在排列中对应的上一个元素,且深度最深的点。
这样就得到了两棵新树,可以对两个 \(\mathrm{par}\) 数组建立倍增数组。
然后对于询问的路径 \(u\to v\),将其拆解为 \(u\to \mathrm{lca} \to v\)(显然,按照路径中访问的点的顺序依次匹配,肯定是能匹配先匹配更优):
- 对于 \(u \to \mathrm{lca}\) 这一段,我们先找到深度最大的 \(P_1\) 的点 \(x\),然后从 \(x\) 开始在 \(\mathrm{par}_1\) 上倍增跳,跳到在原树中深度最小且 \(\geq \mathrm{dep}_{\mathrm{lca}}\) 的点。
- 对于 \(\mathrm{lca} \to v\) 这一段,因为我们已经在前面一段找到了能匹配的最长前缀 \(P_1\sim P_{L_1}\),所以可以二分这一段接着能匹配的长度 \(L_2\)。二分的判断只需要在 \(\mathrm{lca} \to v\) 这一段找到最深的 \(P_{L_1+L_2}\),然后在 \(\mathrm{par}_2\) 上跳 \(L_2-1\) 步看看深度是否满足 \(> \mathrm{dep}\)
注意上面是把第二段的询问离线后 DFS,在 DFS 的时候用一个桶记录了路径上最后一个点权等于每个值的点。同时注意 LCA 必须只能属于其中一段,在另一端查询的时候要去掉 LCA。
因为有二分,所以时间复杂度就是 \(\mathcal O(n \log n + q\log n\log m)\)。
code
#include <bits/stdc++.h>
using std::swap;
using std::cerr;
using std::vector;
typedef std::vector<int> vi;
template <class T>
inline void read(T &x) {
static char ch;
while (!isdigit(ch = getchar()));
x = ch - '0';
while (isdigit(ch = getchar()))
x = x * 10 + ch - '0';
}
template <class T>
inline void putint(T x) {
static char buf[15], *tail = buf;
if (!x) putchar('0');
else {
if (x < 0) putchar('-'), x = ~x + 1;
for (; x; x /= 10) *++tail = x % 10 + '0';
for (; tail != buf; --tail) putchar(*tail);
}
}
const int MaxN = 2e5 + 5;
const int MaxM = 5e4 + 5;
const int MaxE = MaxN << 1;
const int MaxLog = 18;
int dep[MaxN];
struct tree {
int anc[MaxN][MaxLog + 1];
inline void insert(int u, int fa) {
anc[u][0] = fa;
for (int i = 0; anc[u][i]; ++i)
anc[u][i + 1] = anc[anc[u][i]][i];
}
inline int jump(int x, int low) {
int res = 0;
for (int i = MaxLog; i >= 0; --i)
if (anc[x][i] && dep[anc[x][i]] >= low)
res |= 1 << i, x = anc[x][i];
return res + (dep[x] >= low);
}
inline int queryLCA(int u, int v) {
if (dep[u] < dep[v]) swap(u, v);
for (int d = dep[u] - dep[v], i = 0; d; d >>= 1, ++i)
if (d & 1) {
u = anc[u][i];
}
if (u == v) return u;
for (int i = MaxLog; i >= 0; --i)
if (anc[u][i] != anc[v][i]) {
u = anc[u][i];
v = anc[v][i];
}
return anc[u][0];
}
}A, B, T;
struct request {
int d, lca, pos; // d: u->lca answer
request(){}
request(int a, int b, int c): d(a) ,lca(b), pos(c) {}
};
int n, m, Q;
int p[MaxN], idx[MaxN], c[MaxN];
vi adj[MaxN];
int lst[MaxN], src[MaxN];
vector<request> req[MaxN];
int ans[MaxN];
void dfsInit(int u, int pre) {
int tlst = lst[c[u]];
lst[c[u]] = u;
src[u] = lst[p[1]];
dep[u] = dep[pre] + 1;
A.insert(u, lst[p[idx[c[u]] + 1]]);
B.insert(u, lst[p[idx[c[u]] - 1]]);
for (int i = 0; i < (int)adj[u].size(); ++i) {
int v = adj[u][i];
if (v == pre) continue;
T.insert(v, u);
dfsInit(v, u);
}
lst[c[u]] = tlst;
}
void dfsSolve(int u, int pre) {
int tlst = lst[c[u]];
lst[c[u]] = u;
for (int i = 0; i < (int)adj[u].size(); ++i) {
int v = adj[u][i];
if (v == pre) continue;
dfsSolve(v, u);
}
for (int i = 0; i < (int)req[u].size(); ++i) {
request R = req[u][i];
int l = R.d + 1, r = m, mid, res = R.d;
int lim = dep[R.lca] + 1;
while (l <= r) {
mid = (l + r) >> 1;
if (B.jump(lst[p[mid]], lim) >= mid - R.d)
l = mid + 1, res = mid;
else
r = mid - 1;
}
ans[R.pos] = res;
}
lst[c[u]] = tlst;
}
int main() {
freopen("gem.in", "r", stdin);
freopen("gem.out", "w", stdout);
read(n), read(m)/*no use*/, read(m);
for (int i = 1; i <= m; ++i) {
read(p[i]);
idx[p[i]] = i;
}
for (int i = 1; i <= n; ++i) {
read(c[i]);
}
for (int i = 1; i < n; ++i) {
int u, v;
read(u), read(v);
adj[u].push_back(v);
adj[v].push_back(u);
}
dfsInit(1, 0);
read(Q);
for (int q = 1; q <= Q; ++q) {
int u, v;
read(u), read(v);
int lca = T.queryLCA(u, v);
ans[q] = A.jump(src[u], dep[lca]);
// cerr << u << ' ' << v << ':' << lca << ':' << ans[q] << '\n';
if (lca != v) {
req[v].push_back(request(ans[q], lca, q));
}
}
dfsSolve(1, 0);
for (int q = 1; q <= Q; ++q) {
putint(ans[q]);
putchar('\n');
}
return 0;
}
算法二
第二段的询问并不需要二分。可以将第一段已经得到的点放到 \(\mathrm{lca}\) 的地方,离线 DFS 的时候用可撤销并查集维护。
这样总的时间复杂度就是 \(\mathcal O(n \log n + Q \log m)\) 了。
算法三
算法一中的 \(\mathrm{lca}\) 其实可以换一种角度看待。
用点分治处理所有路径询问,对于跨过某个分治中心 \(G\) 的询问,就将 \(G\) 看成算法一中的 \(\mathrm{lca}\) 来处理。
这样的好处就是,可以直接算出到每个点 \(u\) 到 \(G\) 的路径上,在 \(P\) 中往前和往后分别能跳多少步,就不需要倍增了。
每个询问的第二段就像算法一那样二分,但是检查的时候也不需要倍增。
时间复杂度 \(\mathcal O(n \log n + Q\log m)\)。
E. D2T2 滚榜
显然,最后排名的顺序就是滚榜顺序的倒序,所以我们只需要对滚榜顺序进行计数即可。
显然,在滚榜顺序确定的情况下,既然要求通过题数不下降且总和 \(=m\),那么有一种最优构造就是依次取可能的最小值,多出来的放到最后一个滚榜的人身上。
那么判断一个排列是否合法,就相当于判断每个人都取最少题数的时候,总题数是否 \(\leq m\)。
记原先的第一名为 \(p_0\),滚榜顺序为排列 \(p_1\sim p_n\)。显然我们有:
于是我们就可以设计一个状压 DP,设 \(f(s,v,l)\) 表示以下条件的方案数:
- 已经用了集合 \(s\) 中的元素作为排列的前 \(|s|\) 个元素
- 当前
- 最后一个元素,即 \(p_{|s|}=l\)。
转移就再枚举一个 \(x \in [n]\setminus S\),转移到
时间复杂度 \(\mathcal O(2^nn^2m)\),卡卡常数可以过。
另:https://www.luogu.com.cn/blog/gyh20/solution-p7519
题解里面有另一种做法,大概是折半搜索。
code
#include <bits/stdc++.h>
using std::max;
using std::cerr;
using std::cout;
typedef long long s64;
template <class T>
inline void read(T &x) {
static char ch;
while (!isdigit(ch = getchar()));
x = ch - '0';
while (isdigit(ch = getchar()))
x = x * 10 + ch - '0';
}
const int MaxN = 15;
int n, m;
int a[MaxN], p[MaxN];
const int MaxS = 1 << 13 | 5;
const int MaxM = 510;
s64 f[MaxS][MaxM][MaxN];
int main() {
freopen("ranklist.in", "r", stdin);
freopen("ranklist.out", "w", stdout);
read(n), read(m);
a[0] = -1;
for (int i = 1; i <= n; ++i) {
read(a[i]);
if (a[i] > a[p[0]]) p[0] = i;
}
int all = (1 << n) - 1;
for (int i = 1; i <= n; ++i) {
int val = max(0, a[p[0]] - a[i] + (i > p[0])) * n;
if (val <= m) {
f[1 << (i - 1)][val][i] = 1;
}
}
for (int s = 1; s <= all; ++s) {
int cnt = 0, seq[MaxN], nS = 0;
for (int i = 1; i <= n; ++i)
if (s >> (i - 1) & 1)
++cnt;
else
seq[++nS] = i;
for (int v = 0; v <= m; ++v)
for (int lst = 1; lst <= n; ++lst)
if (f[s][v][lst]) {
int val = f[s][v][lst];
for (int j = 1; j <= nS; ++j) {
int i = seq[j];
int c = max(0, a[lst] - a[i] + (i > lst)) * (n - cnt);
if (v + c <= m)
f[s | (1 << (i - 1))][v + c][i] += val;
}
}
}
s64 res = 0;
for (int v = 0; v <= m; ++v)
for (int i = 1; i <= n; ++i)
res += f[all][v][i];
cout << res << '\n';
return 0;
}
F. D2T3 支配
首先我们定义 \(x \operatorname{dom} y\) 当且仅当 \(1 \to y\) 的每条路径都经过了点 \(x\)。不难发现以下性质:
- \(x\operatorname{dom} y,y\operatorname{dom} z\implies x\operatorname{dom} z\)
- \(x\operatorname{dom} y, y\operatorname{dom} x\implies x=y\)
- \(x\operatorname{dom} z, y\operatorname{dom} z\implies x\operatorname{dom} y \operatorname{\lor} y\operatorname{dom} x\)
于是对于一个点 \(x(x\neq 1)\),\(D_x\) 中的点可以根据它们之间的支配关系排成一条链。
我们总能找到唯一一个 \(y\in D_x\setminus\{x\}\),使得 \(\forall z\in D_x\setminus\{x\} , z\operatorname{dom} y\),这里的 \(y\) 就是 \(D_x\setminus\{x\}\) 中 \(|D_y|\) 最大的点。将 \(y\) 往 \(x\) 连边,我们就会连出一个 \(n\) 个点的图,每个点入度 \(\leq 1\),也不存在环,而 \(1\) 又支配所有点,因此一定是一棵有根外向树,我们就称之为支配树。
很容易发现,\(D_x\) 即这个支配树上 \(x\) 的祖先集合 \(\operatorname{anc}(x)\) 并上自己,即 \(D_x=\operatorname{anc}(x)\cup \{x\}\)。
那么首先建出原图的支配树,这里直接 \(\mathcal O(n(m+n))\) 删去每个点后跑 DFS/BFS 求出受支配集 \(D_x\) 后建出来。
记 \(\mathrm{fa}_x\) 表示 \(x\) 在支配树上的父亲。
那么加一条边后,显然 \(D_x'\subseteq D_x\),新的 \(D_x'\) 就是原来 \(x\) 到根的路径上的一些点。所以如果 \(D_x\) 改变,那么只有可能是 \(\mathrm{fa}_x\) 改变了或者 \(D_{\mathrm{fa}_x}\) 改变了。
而如果 \(\mathrm{fa}_x\) 改变了,\(x\) 子树中的所有点的 \(D_x\) 都会改变。我们只需要考虑每个点 \(x\) 的 \(\mathrm{fa}_x\) 是否还支配 \(x\) 即可。
具体地,对于每个点 \(x\),删去 \(\mathrm{fa}_x\) 后从 \(1\) 开始 DFS/BFS,肯定会有 \(1\) 无法到达 \(x\)(因为 \(\mathrm{fa}_x \operatorname{dom} x\))。此时,记删去 \(\mathrm{fa}_x\) 后的图是 \(G_{x}\),我们将当前这张图点分成三类:
- 从 \(1\) 开始能够到达的点构成点集 \(A_x\)。
- 从 \(y\) 出发能够走到 \(x\) 的点构成点集 \(B_x\)。
- 其余点构成点集 \(C_x\)。
显然,一条连边 \((u,v)\) 会使 \(\mathrm{fa}_x\) 不再支配 \(x\),当且仅当 \(u\in A_x\land v\in B_x\)。
依次判断即可做到 \(\mathcal O(n(m+n)+Qn)\) 的时间复杂度。
code
#include <bits/stdc++.h>
#define foredge(u) for (int e = adj[u], v; v = to[e], e; e = nxt[e])
using std::cerr;
using std::vector;
typedef vector<int> vi;
template <class T>
inline void read(T &x) {
static char ch;
while (!isdigit(ch = getchar()));
x = ch - '0';
while (isdigit(ch = getchar()))
x = x * 10 + ch - '0';
}
template <class T>
inline void putint(T x) {
static char buf[15], *tail = buf;
if (!x) putchar('0');
else {
if (x < 0) putchar('-'), x = ~x + 1;
for (; x; x /= 10) *++tail = x % 10 + '0';
for (; tail != buf; --tail) putchar(*tail);
}
}
const int MaxN = 3e3 + 5;
const int MaxE = MaxN << 2;
int n, m, Q;
int par[MaxN];
vi dom[MaxN], adj[MaxN], adjR[MaxN];
void bfs(int del) {
static int que[MaxN], qr;
static bool vis[MaxN];
for (int i = 1; i <= n; ++i) {
vis[i] = false;
}
if (del != 1) {
vis[que[qr = 1] = 1] = true;
for (int i = 1; i <= qr; ++i) {
int u = que[i];
for (int ei = 0; ei < (int)adj[u].size(); ++ei) {
int v = adj[u][ei];
if (!vis[v] && v != del) {
vis[que[++qr] = v] = true;
}
}
}
}
for (int i = 1; i <= n; ++i)
if (!vis[i]) dom[i].push_back(del);
}
int dfn[MaxN];
int tag[MaxN][MaxN];
void makeTag(int del, int st) {
if (del == 1) return;
static int que[MaxN], qr;
static bool vis[MaxN];
for (int i = 1; i <= n; ++i) vis[i] = false;
vis[que[qr = 1] = 1] = true;
for (int i = 1; i <= qr; ++i) {
int u = que[i];
tag[u][dfn[st]] = 1;
for (int ei = 0; ei < (int)adj[u].size(); ++ei) {
int v = adj[u][ei];
if (!vis[v] && v != del) {
vis[que[++qr] = v] = true;
}
}
}
for (int i = 1; i <= n; ++i) vis[i] = false;
vis[que[qr = 1] = st] = true;
for (int i = 1; i <= qr; ++i) {
int u = que[i];
tag[u][dfn[st]] = 2;
for (int ei = 0; ei < (int)adjR[u].size(); ++ei) {
int v = adjR[u][ei];
if (!vis[v] && v != del) {
vis[que[++qr] = v] = true;
}
}
}
}
vi adjT[MaxN];
int dfsClock, idx[MaxN], sze[MaxN];
void dfsInit(int u) {
idx[dfn[u] = ++dfsClock] = u;
for (int ei = 0; ei < (int)adjT[u].size(); ++ei) {
int v = adjT[u][ei];
dfsInit(v);
}
}
int main() {
freopen("dominator.in", "r", stdin);
freopen("dominator.out", "w", stdout);
read(n), read(m), read(Q);
for (int i = 1; i <= m; ++i) {
int u, v;
read(u), read(v);
adj[u].push_back(v);
adjR[v].push_back(u);
}
for (int i = 1; i <= n; ++i) {
bfs(i);
}
for (int u = 1; u <= n; ++u) {
for (int i = 0; i < (int)dom[u].size(); ++i) {
int v = dom[u][i];
if (dom[v].size() == dom[u].size() - 1) {
par[u] = v;
break;
}
}
if (u > 1) adjT[par[u]].push_back(u);
// cerr << u << ':' << par[u] << '\n';
}
dfsInit(1);
for (int u = 2; u <= n; ++u) {
makeTag(par[u], u);
}
while (Q--) {
int x, y;
read(x), read(y);
static bool chg[MaxN];
int ans = 0;
for (int i = 2; i <= n; ++i) {
int u = idx[i];
chg[u] = chg[par[u]] | (tag[x][i] == 1 && tag[y][i] == 2);
ans += chg[u];
}
putint(ans), putchar('\n');
}
return 0;
}
G. B卷 D1T1 数对
略。
H. B卷 D2T1 取模
先将 \(a_1\sim a_n\) 全部排序。
假设当前的模数是 \(a_k\),记 \(b_i=a_i \bmod a_k\)。
假设选出的两个数为 \(i,j(i\neq j,i\neq k,j\neq k)\):
- 若 \(b_i + b_j \geq a_k\),这个时候我们只要找 \(b_i,b_j\) 最大的两个相加计算即可。
- 若 \(b_i + b_j < a_k\),这个时候我们只要对于每个 \(b_i\),找到最大的满足 \(b_i + b_j < a_k\) 的 \(b_j\) 即可,排序后用双指针扫描。
但是如果对于每个 \(a_k\) 都这么做的话,时间复杂度显然是 \(\mathcal O(n^2 \log n)\) 的。
但是我们只需要从大到小枚举 \(a_k\),并且储存当前的最优答案 \(\mathrm{ans}\),在 \(a_k - 1 > \mathrm{ans}\) 的时候才去计算,否则就结束程序。
显然需要计算的是一段后缀。假设当前考虑到了 \(a_k\),那么
显然
注意这个分析在相同的 \(a_i\) 不超过两个的时候才是成立的,否则时间复杂度就会可能是错的。
因此
注意到 \(a_i - \mathrm{ans} > 0\),因此 \(a_i-\mathrm{ans}=\mathcal O(V)\) 是斐波那契数列级别的增长(\(V\) 表示 \(a\) 的最大值),所以需要枚举的个数是 \(\mathcal O(\log V)\) 的。
因此时间复杂度为 \(\mathcal O(n \log n \log V)\)。
#include <bits/stdc++.h>
template <class T>
inline void read(T &x) {
static char ch;
while (!isdigit(ch = getchar()));
x = ch - '0';
while (isdigit(ch = getchar()))
x = x * 10 + ch - '0';
}
template <class T>
inline void relax(T &x, const T &y) {
if (x < y) x = y;
}
const int MaxN = 2e5 + 5;
int ans;
int n, a[MaxN];
void solve(int p, int del) {
static int b[MaxN], m;
m = 0;
for (int i = 1; i <= n; ++i) {
if (i != del) b[++m] = a[i] % p;
}
std::sort(b + 1, b + m + 1);
relax(ans, (b[m - 1] + b[m]) % p);
for (int i = 1, lst = m; i <= m; ++i) {
while (lst > i && b[lst] + b[i] >= p) --lst;
if (lst <= i) return;
relax(ans, b[i] + b[lst]);
}
}
int main() {
freopen("mod.in", "r", stdin);
freopen("mod.out", "w", stdout);
read(n);
for (int i = 1; i <= n; ++i) {
read(a[i]);
}
std::sort(a + 1, a + n + 1);
ans = 0;
for (int i = n; i >= 1; --i)
if (a[i] - 1 > ans && (i == 1 || a[i] != a[i - 1]))
solve(a[i], i);
std::cout << ans << '\n';
return 0;
}