2019牛客暑期多校训练营(第五场)
Contest Info
[Practice Link](https://ac.nowcoder.com/acm/contest/885#question)
Solved | A | B | C | D | E | F | G | H | I | J |
---|---|---|---|---|---|---|---|---|---|---|
9/10 | O | Ø | Ø | - | Ø | Ø | O | O | O | Ø |
- O 在比赛中通过
- Ø 赛后通过
- ! 尝试了但是失败了
- - 没有尝试
Solutions
A. digits 2
题意:
给出一个数\(n(1 \leq n \leq 100)\),要求构造一个长度小于\(10^4\)的数,并且它的位数之和是\(n\)是倍数,它本身也是\(n\)的倍数。
思路:
输出\(n\)个\(n\)。
代码:
#include <bits/stdc++.h>
using namespace std;
int n;
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
printf("%d", n);
}
puts("");
}
return 0;
}
B. generator 1
题意:
求广义斐波那契数列的第\(n(1 \leq n \leq 10^{10^6})\)项。
思路:
十进制矩阵快速幂
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 1000010
char s[N];
ll x[2], a, b, mod;
inline void add(ll &x, ll y) {
x += y;
if (x >= mod) x -= mod;
}
struct node {
ll a[2][2];
node() {
memset(a, 0, sizeof a);
}
void set() {
memset(a, 0, sizeof a);
a[0][0] = a[1][1] = 1;
}
node operator * (const node &other) const {
node res = node();
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 2; ++j)
for (int k = 0; k < 2; ++k)
add(res.a[i][j], a[i][k] * other.a[k][j] % mod);
return res;
}
node operator ^ (ll n) {
node res = node(), base = *this; res.set();
while (n) {
if (n & 1) res = res * base;
base = base * base;
n >>= 1;
}
return res;
}
}base, res;
int main() {
while (scanf("%lld%lld%lld%lld", x, x + 1, &a, &b) != EOF) {
scanf("%s%lld", s + 1, &mod);
int len = strlen(s + 1);
base.a[0][0] = a; base.a[0][1] = 1;
base.a[1][0] = b; base.a[1][1] = 0;
res = node();
res.a[0][0] = x[1];
res.a[0][1] = x[0];
for (int i = len; i >= 1; --i) {
int num = s[i] - '0';
node tmp = base;
while (num) {
if (num & 1) res = res * tmp;
tmp = tmp * tmp;
num >>= 1;
}
base = base ^ 10;
}
printf("%lld\n", res.a[0][1]);
}
return 0;
}
C.generator 2
题意:
给出一个线性同余生成器生成的序列\(a_i\),\(q\)次询问某个数\(v_i\)最早出现的下标。
思路:
考虑\(a_n = c_{a_{n - 1}} + d)(c \neq 0, c \neq 1)\)可以求出通项公式,变换后有:
那么当\(c = 0, c = 1\)的时候可以特判,其他情况采用\(BSGS\)求解。
但是注意对于一组数据,询问次数较多,可以在块的大小考虑不分为\(O(\sqrt{p})\)而是分为\(O(\sqrt{pq})\)。
这样预处理的时间复杂度是\(O(\sqrt{pq})\),而询问的总复杂度也是\(O(\sqrt{pq})\)。
E. independent set 1
题意:
给出一张\(n\)个点,\(m\)条边的无向图,问它所有的点集的子集中的最大独立集的和是多少。
思路:
用二进制状态表示一个点集,一个点相邻点的状态也可以用二进制表示。
那么令\(f[x]\)表示点集\(x\)的最大独立集大小,那么我们用\(lb(x)\)表示\(x\)的最低位为\(1\)的位是多少。
那么可以这么转移:
即表示\(lb(x)\)这个点取和不取的两种状态。
代码:
#include <bits/stdc++.h>
using namespace std;
int n, m;
int e[30];
char f[1 << 26];
char max(char x, int y) {
if (x > y) return x;
else return y;
}
int main() {
while (scanf("%d%d", &n, &m) != EOF) {
for (int i = 0; i < n; ++i) e[i] = 1 << i;
for (int i = 1, u, v; i <= m; ++i) {
scanf("%d%d", &u, &v);
e[u] |= 1 << v;
e[v] |= 1 << u;
}
for (int i = 0; i < n; ++i) {
e[i] = ~e[i];
}
memset(f, 0, sizeof f);
int ans = 0;
int it = 0;
for (int i = 1; i < 1 << n; ++i) {
for (int j = 0; j < n; ++j)
if ((i >> j) & 1) {
it = j;
break;
}
f[i] = max(f[i ^ (1 << it)], f[i & e[it]] + 1);
ans += f[i];
}
printf("%d\n", ans);
}
return 0;
}
F. maximum clique 1
题意:
给出一个序列\(a_i\),要求找出尽量多的数,使得任意两个数之间至少有两位二进制位不同。
输出方案。
思路:
考虑任意两个数之间至少有两位二进制位不同,那么将所有边连起来构成一个图,那么相当于找一个最大团。
再考虑最大团 = 补图的最大独立集。
我们考虑补图,任意两个不同的数至少有两位二进制位不同的否命题相当于恰好有一位二进制位不同。
那么我们将数划分成二进制位上\(1\)的个数为奇数和偶数的。
那么这是一张二分图。
然后求二分图最大独立集输出方案即可。
为什么是二分图?
我们考虑\(x、y\)都是二进制位上\(1\)的个数全是奇数的。
那么如果有一位不同的话,说明有一位上\(x = 0, y = 1\),那么这时候如果它们其他位都相同的话,那么\(1\)的个数的奇偶性显然不同。
所以二进制位上\(1\)的个数的奇偶性相同的数之间是不会连边的。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define INFLL 0x3f3f3f3f3f3f3f3f
struct Dicnic {
static const int M = 2e6 + 10;
static const int N = 1e5 + 10;
struct Edge {
int to, nxt;
ll flow;
Edge() {}
Edge(int to, int nxt, ll flow) : to(to), nxt(nxt), flow(flow) {}
} edge[M];
int n, S, T;
int head[N], tot;
int dep[N];
void init(int n, int S, int T) {
this->n = n;
this->S = S;
this->T = T;
memset(head, -1, sizeof head);
tot = 0;
}
void addedge(int u, int v, int w, int rw = 0) {
edge[tot] = Edge(v, head[u], w);
head[u] = tot++;
edge[tot] = Edge(u, head[v], rw);
head[v] = tot++;
}
bool BFS() {
memset(dep, -1, sizeof dep);
queue<int> q;
q.push(S);
dep[S] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; ~i; i = edge[i].nxt) {
if (edge[i].flow && dep[edge[i].to] == -1) {
dep[edge[i].to] = dep[u] + 1;
q.push(edge[i].to);
}
}
}
return dep[T] >= 0;
}
ll DFS(int u, ll f) {
if (u == n || f == 0) return f;
ll w, used = 0;
for (int i = head[u]; ~i; i = edge[i].nxt) {
if (edge[i].flow && dep[edge[i].to] == dep[u] + 1) {
w = DFS(edge[i].to, min(f - used, edge[i].flow));
edge[i].flow -= w;
edge[i ^ 1].flow += w;
used += w;
if (used == f) return f;
}
}
if (!used) dep[u] = -1;
return used;
}
ll solve() {
ll ans = 0;
while (BFS()) {
ans += DFS(S, INFLL);
}
return ans;
}
}dicnic;
#define N 5010
int n, a[N];
bool isleft[N];
bool ispow2(int x) {
return x && (x & (x - 1)) == 0;
}
int getbit(int x) {
int res = 0;
while (x) {
res += x % 2;
x /= 2;
}
return res & 1;
}
int main() {
while (scanf("%d", &n) != EOF) {
int S = 0, T = n + 1;
dicnic.init(n + 1, S, T);
for (int i = 1; i <= n; ++i) {
scanf("%d", a + i);
isleft[i] = getbit(a[i]);
if (isleft[i]) dicnic.addedge(S, i, 1);
else dicnic.addedge(i, T, 1);
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j < i; ++j) {
if (ispow2(a[i] ^ a[j])) {
if (isleft[i]) dicnic.addedge(i, j, 1);
else dicnic.addedge(j, i, 1);
}
}
}
int num = n - dicnic.solve();
vector <int> res;
for (int i = 1; i <= n; ++i) {
if (isleft[i]) {
if (dicnic.dep[i] != -1) {
res.push_back(a[i]);
}
} else {
if (dicnic.dep[i] == -1) {
res.push_back(a[i]);
}
}
}
printf("%d\n", num);
for (int i = 0; i < num; ++i)
printf("%d%c", res[i], " \n"[i == num - 1]);
}
return 0;
}
G. subsequence 1
题意:
给出两个字符串\(s、t\),问\(s\)中有多少个子序列的数值表示比\(t\)的数值表示要大。
选出的子序列不能有前导\(0\),\(s、t\)本身没有前导\(0\)。
思路:
考虑选出的子序列长度大于\(|t|\)的部分可以直接用组合数计算。
那么只需要考虑长度相同的,令\(f[i][x][0/1]\)表示考虑\(s\)的前\(i\)个字符,已经选了\(x\)位了,大于/恰好等于\(t\)的前\(x\)位的方案数。
那么直接枚举下一位转移即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 3010
const ll p = 998244353;
int n, m;
char s[N], t[N];
ll dp[N][2];
void add(ll &x, ll y) {
x += y;
if (x >= p) x -= p;
}
ll qmod(ll base, ll n) {
ll res = 1;
while (n) {
if (n & 1) {
res = res * base % p;
}
base = base * base % p;
n >>= 1;
}
return res;
}
ll fac[N], inv[N];
ll C(int n, int m) {
return fac[n] * inv[m] % p * inv[n - m] % p;
}
int main() {
fac[0] = 1;
for (int i = 1; i < N; ++i) fac[i] = fac[i - 1] * i % p;
inv[N - 1] = qmod(fac[N - 1], p - 2);
for (int i = N - 1; i >= 1; --i) inv[i - 1] = inv[i] * i % p;
int T; scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
scanf("%s%s", s + 1, t + 1);
for (int i = 0; i <= n; ++i) {
dp[i][0] = dp[i][1] = 0;
}
for (int i = 1; i <= n; ++i) {
for (int j = m - 1; j >= 1; --j) {
ll f;
if (s[i] == t[j + 1]) {
f = dp[j][1];
add(dp[j + 1][1], f);
} else if (s[i] > t[j + 1]) {
f = dp[j][1];
add(dp[j + 1][0], f);
}
f = dp[j][0];
add(dp[j + 1][0], f);
}
if (s[i] != '0') {
if (s[i] == t[1]) {
add(dp[1][1], 1);
} else if (s[i] > t[1]) {
add(dp[1][0], 1);
}
}
}
ll res = dp[m][0];
for (int i = 1; i <= n; ++i) {
if (s[i] != '0') {
int tot = n - i;
for (int j = m + 1; j <= n - i + 1; ++j) {
add(res, C(tot, j - 1));
}
}
}
printf("%lld\n", res);
}
return 0;
}
H. subsequence 2
题意:
有一个字符串\(s\),由前\(m\)的小写字符构成,现在给出任意两个小写字符构成的子序列(去掉其他字符后的),问能否还原出字符串\(s\)。
思路:
- 连边的时候只需要向前一个点连边即可。
- 如果字符个数超过\(n\),就不满足
- 同一字符在不同组合中出现的字母个数要相同
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 1000010
int n, m, id;
vector <vector<int>> G;
int st[N], d[N];
char mp[N], s[N];
int num[N], x, y;
int ord[N], cnt;
void Toposort() {
cnt = 0;
queue <int> q;
for (int i = 1; i <= id; ++i) {
if (d[i] == 0)
q.push(i);
}
while (!q.empty()) {
int u = q.front(); q.pop();
ord[++cnt] = u;
for (auto v : G[u]) {
if (--d[v] == 0) {
q.push(v);
}
}
}
if (cnt == id && id == n) {
for (int i = 1; i <= id; ++i)
putchar(mp[ord[i]]);
puts("");
} else {
puts("-1");
}
}
int main() {
while (scanf("%d%d", &n, &m) != EOF) {
id = 0;
G.clear(); G.resize(N);
memset(st, -1, sizeof st);
memset(num, -1, sizeof num);
memset(d, 0, sizeof d);
int len, cntx, cnty;
bool F = 1;
for (int i = 1; i <= (m * (m - 1)) / 2; ++i) {
scanf(" %s %d ", s, &len);
x = s[0], y = s[1];
if (len) {
scanf("%s", s + 1);
}
cntx = cnty = 0;
for (int i = 1; i <= len; ++i) {
if (s[i] == x) {
++cntx;
} else {
++cnty;
}
}
if (num[x] == -1) {
num[x] = cntx;
} else if (num[x] != cntx) {
F = 0;
}
if (num[y] == -1) {
num[y] = cnty;
} else if (num[y] != cnty) {
F = 0;
}
if (len == 0) continue;
if (st[x] == -1) {
for (int i = 1; i <= cntx; ++i) {
mp[++id] = x;
if (i > 1) {
G[id - 1].push_back(id);
++d[id];
} else {
st[x] = id;
}
}
}
if (st[y] == -1) {
for (int i = 1; i <= cnty; ++i) {
mp[++id] = y;
if (i > 1) {
G[id - 1].push_back(id);
++d[id];
} else {
st[y] = id;
}
}
}
int posx = st[x], posy = st[y];
--posx, --posy;
if (s[1] == x) ++posx;
else if (s[1] == y) ++posy;
else F = 0;
for (int i = 2; i <= len; ++i) {
if (s[i] != x && s[i] != y) {
F = 0;
break;
}
if (s[i] == x) {
++posx;
if (posy >= st[y]) {
G[posy].push_back(posx);
++d[posx];
}
} else {
++posy;
if (posx >= st[x]) {
G[posx].push_back(posy);
++d[posy];
}
}
}
}
if (F && id == n) {
Toposort();
} else {
puts("-1");
}
}
return 0;
}
I. three points 1
题意:
在二维平面上,要求选出三个点\(x, y, z\),使得:
J. three points 2
题意:
给出一个\(n\)个点的树,问能否在树上找出三点\(X、Y、Z\),使得:
思路:
考虑假如存在这三点的话,那么必定存在一个点\(O\),使得:
那么我们就发现有两个限制条件:
- \(a + b + c\)要是\(2\)的倍数
- \(a + b + c - max(a, b, c) >= max(a, b, c)\)
那么现在的问题就是如何找点\(O\): - 我们可以考虑用\(f[u][3]\)表示以\(u\)为根的子树中,距离它最远,次远,第三远的点的距离和下标分别是多少
- 用\(g[u]\)表示从\(u\)的父亲方向过来的点中最远的点是多少
那么这样我们就能知道对于每个点\(u\),从三个不同方向过来的前三远的点\((x, y, z)\)是多少。
那么对于每个询问,我们将\((p, q, r) = \{dis(O, X), dis(O, Y), dis(O, Z)\}\)排序,然后去找是否存在点\(O\)。
可以用扫描线 + 树状数组的方法去找:
- 考虑将所有点和询问按第一关键字排序
- 然后以第二关键字为下标在树状数组中插入第三关键字以及它的\(id\)
- 然后查询的时候就查询第二关键字往上的所有第三关键字的最大值是否大于自己的第三关键字,如果是,那么这个点就是一个可行的\(O\)点。
那么找到\(O\)点之后,再去找\(X, Y, Z\)三点就相当于找第\(k\)大的祖先,倍增即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define dbg(...) { printf("# "); printf(__VA_ARGS__); puts(""); }
#define N 200010
#define M 20
#define pii pair <int, int>
#define fi first
#define se second
#define INF 0x3f3f3f3f
vector <vector<int>> G;
int n, q, m;
struct node {
pii x[3];
int op, id;
node() {}
//0表示插入
//1表示查询
node(pii x0, pii x1, pii x2, int op = 0, int id = -1) : op(op), id(id) {
x[0] = x0; x[1] = x1; x[2] = x2;
sort(x, x + 3);
}
void reset() {
sort(x, x + 3, [](pii a, pii b) {
return a.se < b.se;
});
}
bool operator < (const node &other) const {
for (int i = 0; i < 3; ++i) {
if (x[i].fi != other.x[i].fi) {
return x[i].fi > other.x[i].fi;
}
}
return op < other.op;
}
}qrr[N << 1], ans[N];
int fa[N][M], deep[N], in[N], out[N], cnt;
pii f[N][3], g[N];
void DFS(int u) {
in[u] = ++cnt;
f[u][0] = pii(0, u);
f[u][1] = pii(-INF, -1);
f[u][2] = pii(-INF, -1);
for (int i = 1; i < M; ++i)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (auto v : G[u]) {
if (v == fa[u][0]) continue;
fa[v][0] = u;
deep[v] = deep[u] + 1;
DFS(v);
if (f[v][0].fi + 1 > f[u][0].fi) {
f[u][2] = f[u][1];
f[u][1] = f[u][0];
f[u][0] = f[v][0];
++f[u][0].fi;
} else if (f[v][0].fi + 1 > f[u][1].fi) {
f[u][2] = f[u][1];
f[u][1] = f[v][0];
++f[u][1].fi;
} else if (f[v][0].fi + 1 > f[u][2].fi) {
f[u][2] = f[v][0];
++f[u][2].fi;
}
}
out[u] = cnt;
}
void DFS2(int u) {
for (auto v : G[u]) {
if (v == fa[u][0]) continue;
g[v] = g[u]; ++g[v].fi;
if (in[v] <= in[f[u][0].se] && in[f[u][0].se] <= out[v]) {
if (f[u][1].se != -1 && f[u][1].fi + 1 > g[v].fi) {
g[v] = f[u][1];
++g[v].fi;
}
} else {
if (f[u][0].fi + 1 > g[v].fi) {
g[v] = f[u][0];
++g[v].fi;
}
}
DFS2(v);
}
}
int querylca(int u, int v) {
if (deep[u] > deep[v]) swap(u, v);
for (int gap = deep[v] - deep[u], i = 0; gap; gap >>= 1, ++i) {
if (gap & 1) {
v = fa[v][i];
}
}
if (u == v) return u;
for (int i = M - 1; i >= 0; --i) {
if (fa[u][i] == fa[v][i]) continue;
u = fa[u][i];
v = fa[v][i];
}
return fa[u][0];
}
int dis(int u, int v) {
return deep[u] + deep[v] - 2 * deep[querylca(u, v)];
}
int querykth(int u, int k) {
for (int i = M - 1; i >= 0; --i) {
if ((k >> i) & 1) {
u = fa[u][i];
}
}
return u;
}
struct BIT {
pii a[N];
void init() {
memset(a, -1, sizeof a);
}
void update(int x, pii v) {
for (; x > 0; x -= x & -x) {
a[x] = max(a[x], v);
}
}
pii query(int x) {
pii res = pii(-1, -1);
for (; x < N; x += x & -x) {
res = max(res, a[x]);
}
return res;
}
}bit;
int find(int O, int x, int Dis) {
//在子树内
if (in[x] >= in[O] && in[x] <= out[O]) {
return querykth(x, deep[x] - deep[O] - Dis);
} else { //子树外
int lca = querylca(O, x);
if (deep[O] - deep[lca] >= Dis) {
return querykth(O, Dis);
} else {
return querykth(x, deep[x] - deep[lca] - (Dis - deep[O] + deep[lca]));
}
}
}
void work(int x, int y) {
int O = qrr[x].id;
for (int i = 0; i < 3; ++i) {
ans[qrr[y].id].x[i] = pii(find(O, qrr[x].x[i].se, qrr[y].x[i].fi), qrr[y].x[i].se);
}
}
int main() {
while (scanf("%d", &n) != EOF) {
G.clear(); G.resize(n + 1); cnt = 0;
for (int i = 1, u, v; i < n; ++i) {
scanf("%d%d", &u, &v);
++u, ++v;
G[u].push_back(v);
G[v].push_back(u);
}
fa[1][0] = 0;
g[1] = pii(0, 1);
deep[1] = 0;
DFS(1);
DFS2(1);
scanf("%d", &q);
m = 0;
for (int i = 1, a, b, c; i <= q; ++i) {
scanf("%d%d%d", &a, &b, &c);
int Max = max(a, max(b, c));
int Sum = a + b + c;
if (Sum - Max < Max || Sum % 2) {
ans[i].x[0].fi = -1;
} else {
qrr[++m] = node(pii((a + b - c) / 2, 0), pii((a + c - b) / 2, 1), pii((b + c - a) / 2, 2), 1, i);
}
}
vector <pii> vec;
for (int i = 1; i <= n; ++i) {
vec.clear();
for (int j = 0; j < 3; ++j) {
if (f[i][j].se != -1) {
vec.push_back(f[i][j]);
}
}
vec.push_back(g[i]);
sort(vec.begin(), vec.end(), [](pii x, pii y) {
return x.fi > y.fi;
});
vec.erase(unique(vec.begin(), vec.end()), vec.end());
if ((int)vec.size() >= 3) {
qrr[++m] = node(vec[0], vec[1], vec[2], 0, i);
}
}
sort(qrr + 1, qrr + 1 + m);
bit.init();
for (int i = 1; i <= m; ++i) {
if (qrr[i].op == 0) {
bit.update(qrr[i].x[1].fi, pii(qrr[i].x[2].fi, i));
} else {
pii tmp = bit.query(qrr[i].x[1].fi);
if (tmp.fi >= qrr[i].x[2].fi) {
work(tmp.se, i);
} else {
ans[qrr[i].id].x[0].fi = -1;
}
}
}
for (int i = 1; i <= q; ++i) {
if (ans[i].x[0].fi == -1) puts("-1");
else {
ans[i].reset();
printf("%d %d %d\n", ans[i].x[0].fi - 1, ans[i].x[1].fi - 1, ans[i].x[2].fi - 1);
}
}
}
return 0;
}