2019牛客暑期多校训练营(第四场)
Contest Info
[Practice Link](https://ac.nowcoder.com/acm/contest/884#question)
Solved | A | B | C | D | E | F | G | H | I | J | K |
---|---|---|---|---|---|---|---|---|---|---|---|
7/11 | O | Ø | O | O | - | - | - | - | Ø | O | O |
- O 在比赛中通过
- Ø 赛后通过
- ! 尝试了但是失败了
- - 没有尝试
Solutions
A. meeting
题意:
给出一棵\(n\)个点的树,现在有\(k\)个人在不同的位置上,要求让他们集合到一点,定义代价为每个人走过的边数的最大值,问选择一点使得代价最少,输出最少代价。
思路:
考虑题意就是选一个根,使得这\(k\)个人所在位置的最大深度最小。
那么令:
- \(f[i]\)表示以\(i\)为根的子树中,最大深度为多少。
- \(g[i]\)表示除了\(i\)的子树中,其他点到\(i\)的最大距离多少。
然后两遍\(DFS\)即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 100010
int n, k;
vector <vector<int>> G;
int f[N][2], pos[N][2], g[N], vis[N], res;
int deep[N], fa[N];
void DFS(int u) {
if (vis[u]) {
f[u][0] = deep[u];
pos[u][0] = u;
f[u][1] = -1;
pos[u][1] = -1;
} else {
f[u][0] = -1;
f[u][1] = -1;
pos[u][0] = -1;
pos[u][1] = -1;
}
for (auto v : G[u]) if (v != fa[u]) {
deep[v] = deep[u] + 1;
fa[v] = u;
DFS(v);
if (f[v][0] > f[u][0]) {
f[u][1] = f[u][0];
pos[u][1] = pos[u][0];
f[u][0] = f[v][0];
pos[u][0] = v;
} else if (f[v][0] > f[u][1]) {
f[u][1] = f[v][0];
pos[u][1] = v;
}
}
}
void DFS2(int u) {
if (u == 1) {
g[u] = -1;
} else {
g[u] = -1;
int pre = fa[u];
if (g[pre] != -1) g[u] = g[pre] + 1;
if (f[pre][0] != -1 && pos[pre][0] != u) {
g[u] = max(g[u], 1 + f[pre][0] - deep[pre]);
}
if (f[pre][1] != -1 && pos[pre][1] != u) {
g[u] = max(g[u], 1 + f[pre][1] - deep[pre]);
}
}
res = min(res, max(f[u][0] - deep[u], g[u]));
for (auto v : G[u]) if (v != fa[u]) {
DFS2(v);
}
}
int main() {
while (scanf("%d%d", &n, &k) != EOF) {
G.clear(); G.resize(n + 1);
for (int i = 1, u, v; i < n; ++i) {
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
memset(vis, 0, sizeof vis);
for (int i = 1, x; i <= k; ++i) {
scanf("%d", &x);
vis[x] = 1;
}
fa[1] = 0; deep[1] = 0;
res = 1e9;
DFS(1);
DFS2(1);
// for (int i = 1; i <= n; ++i) printf("%d %d %d %d\n", i, f[i][0] - deep[i], f[i][1], g[i]);
printf("%d\n", res);
}
return 0;
}
B.xor
题意:
有\(n\)个集合,每个集合的数的个数不超过\(32\)个,现在每次询问\((l, r, x)\),问\([l, r]\)范围内的每个集合是否都能选择若干个数异或起来来表达\(x\)。
思路:
其实问题等价于\([l, r]\)范围的每个集合的线性基的交能否表达\(x\),这个时间复杂度是\(\mathcal{O}(32^2n + 32^2qlogn)\)。
或者直接暴力\(check\)每个线性基能否表达\(x\),这个复杂度是\(\mathcal{O}(32qn)\)
那么我们考虑结合一下,我们建一棵线段树,需要\(O(n)\)次求交,然后查询的时候在分出的\(logn\)个区间中分别查询,那么复杂度为\(\mathcal{O}(32^2n + 32qlogn)\)
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 50010
#define u32 unsigned int
int n, q;
u32 a[N][33];
struct Base {
static const int L = 32;
u32 a[L];
Base() {
memset(a, 0, sizeof a);
}
u32& operator [](int x) {
return a[x];
}
u32 operator [](int x) const {
return a[x];
}
void insert(u32 x) {
for (int i = L - 1; i >= 0; --i) {
if ((x >> i) & 1) {
if (a[i]) x ^= a[i];
else {
a[i] = x;
break;
}
}
}
}
bool check(u32 x) {
for (int i = L - 1; i >= 0; --i) {
if ((x >> i) & 1) {
if (a[i]) x ^= a[i];
else return 0;
}
}
return 1;
}
//线性基求交
friend Base intersection(const Base &a, const Base &b) {
Base ans = Base(), c = b, d = b;
for (int i = 0; i <= L - 1; ++i) {
u32 x = a[i];
if (!x) continue;
int j = i; u32 T = 0;
for (; j >= 0; --j) {
if ((x >> j) & 1) {
if (c[j]) {
x ^= c[j];
T ^= d[j];
} else {
break;
}
}
}
if (!x) ans[i] = T;
else {
c[j] = x;
d[j] = T;
}
}
return ans;
}
};
struct SEG {
Base t[N << 2];
void build(int id, int l, int r) {
if (l == r) {
t[id] = Base();
for (int j = 1; j <= a[l][0]; ++j) {
t[id].insert(a[l][j]);
}
return;
}
int mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
t[id] = intersection(t[id << 1], t[id << 1 | 1]);
}
bool query(int id, int l, int r, int ql, int qr, int x) {
if (l >= ql && r <= qr) {
return t[id].check(x);
}
int mid = (l + r) >> 1;
if (ql <= mid && !query(id << 1, l, mid, ql, qr, x)) return 0;
if (qr > mid && !query(id << 1 | 1, mid + 1, r, ql, qr, x)) return 0;
return 1;
}
}seg;
int main() {
while (scanf("%d%d", &n, &q) != EOF) {
for (int i = 1; i <= n; ++i) {
scanf("%u", a[i]);
for (int j = 1; j <= a[i][0]; ++j) {
scanf("%u", a[i] + j);
}
}
seg.build(1, 1, n);
int l, r, x;
while (q--) {
scanf("%d%d%d", &l, &r, &x);
puts(seg.query(1, 1, n, l, r, x) ? "YES" : "NO");
}
}
return 0;
}
C. sequence
题意:
给出两个序列\(a_i\), \(b_i\),求下列式子:
思路:
考虑处理出每个点\(i\)的管辖范围\(l, r\),那么分成两段\([l, i - 1]\)和\([i, r]\)。
然后查询左段的前缀和最大最小值,右段的前缀和最大最小值,更新答案即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define pii pair <int, int>
#define fi first
#define se second
#define ll long long
#define INFLL 0x3f3f3f3f3f3f3f3f
#define N 3000010
int n, a[N];
ll b[N];
struct Cartesian_Tree {
struct node {
int id, val, fa;
int son[2];
node() {}
node (int id, int val, int fa) : id(id), val(val), fa(fa) {
son[0] = son[1] = 0;
}
}t[N];
int root;
pii b[N];
void init() {
t[0] = node(0, -1e9, 0);
}
void build(int n, int *a) {
for (int i = 1; i <= n; ++i) {
t[i] = node(i, a[i], 0);
}
for (int i = 1; i <= n; ++i) {
int k = i - 1;
while (t[k].val > t[i].val) {
k = t[k].fa;
}
t[i].son[0] = t[k].son[1];
t[k].son[1] = i;
t[i].fa = k;
t[t[i].son[0]].fa = i;
}
root = t[0].son[1];
}
int DFS(int u) {
if (!u) return 0;
int lsze = DFS(t[u].son[0]);
int rsze = DFS(t[u].son[1]);
b[t[u].id] = pii(lsze, rsze);
return lsze + rsze + 1;
}
}CT;
struct SEG {
struct node {
ll Max, Min;
node() {
Max = -INFLL;
Min = INFLL;
}
node operator + (const node &other) const {
node res = node();
res.Max = max(Max, other.Max);
res.Min = min(Min, other.Min);
return res;
}
}t[N << 2], resl, resr;
void build(int id, int l, int r) {
if (l == r) {
t[id] = node();
t[id].Max = t[id].Min = b[l];
return;
}
int mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
t[id] = t[id << 1] + t[id << 1 | 1];
}
node query(int id, int l, int r, int ql, int qr) {
if (l >= ql && r <= qr) {
return t[id];
}
int mid = (l + r) >> 1;
node res = node();
if (ql <= mid) res = res + query(id << 1, l, mid, ql, qr);
if (qr > mid) res = res + query(id << 1 | 1, mid + 1, r, ql, qr);
return res;
}
}seg;
namespace IO
{
const int S=(1<<20)+5;
//Input Correlation
char buf[S],*H,*T;
inline char Get()
{
if(H==T) T=(H=buf)+fread(buf,1,S,stdin);
if(H==T) return -1;return *H++;
}
inline int read()
{
int x=0,fg=1;char c=Get();
while(!isdigit(c)&&c!='-') c=Get();
if(c=='-') fg=-1,c=Get();
while(isdigit(c)) x=x*10+c-'0',c=Get();
return x*fg;
}
inline void reads(char *s)
{
char c=Get();int tot=0;
while(c<'a'||c>'z') c=Get();
while(c>='a'&&c<='z') s[++tot]=c,c=Get();
s[++tot]='\0';
}
//Output Correlation
char obuf[S],*oS=obuf,*oT=oS+S-1,c,qu[55];int qr;
inline void flush(){fwrite(obuf,1,oS-obuf,stdout);oS=obuf;}
inline void putc(char x){*oS++ =x;if(oS==oT) flush();}
template <class I>inline void print(I x)
{
if(!x) putc('0');
if(x<0) putc('-'),x=-x;
while(x) qu[++qr]=x%10+'0',x/=10;
while(qr) putc(qu[qr--]);
putc('\n');
}
inline void prints(const char *s)
{
int len=strlen(s);
for(int i=0;i<len;i++) putc(s[i]);
putc('\n');
}
inline void printd(int d,double x)
{
long long t=(long long)floor(x);
print(t);putc('.');x-=t;
while(d--)
{
double y=x*10;x*=10;
int c=(int)floor(y);
putc(c+'0');x-=floor(y);
}
}
}using namespace IO;
int main() {
n = read();
for (int i = 1; i <= n; ++i) a[i] = read();
b[0] = 0;
for (int i = 1; i <= n; ++i) {
b[i] = read();
b[i] += b[i - 1];
}
CT.init();
CT.build(n, a);
CT.DFS(CT.root);
ll res = -INFLL;
seg.build(1, 0, n);
for (int i = 1; i <= n; ++i) {
int l = i - CT.b[i].fi, r = i + CT.b[i].se;
seg.resl = seg.query(1, 0, n, l - 1, i - 1);
seg.resr = seg.query(1, 0, n, i, r);
res = max(res, 1ll * a[i] * (seg.resr.Max - seg.resl.Min));
res = max(res, 1ll * a[i] * (seg.resr.Min - seg.resl.Max));
}
printf("%lld\n", res);
return 0;
}
D. triples I
题意:
给出一个数\(a\),要求选择尽量少的数,使得这些数是\(3\)的倍数,并且它们\(or\)起来等于\(a\)。
题目保证有解。
思路:
- 考虑二进制位当中每一位都是模\(3\)余\(2\),或者模\(3\)余\(1\)。
- 如果\(a \equiv 0 \bmod 3\),那么直接输出\(a\)。
- 那么考虑\(a\)中如果所有二进制位模\(3\)的余数都相同,那么直接取三个位的\(or\)值作为\(A\),然后根据\(a\)模\(3\)的余数,考虑\(a\)中要去掉一位或者两位。
- 再考虑\(a\)中同时有模\(3\)与\(1\)和\(2\)的位,那么对于\(A\),直接各取一位,然后根据\(a\)模\(3\)的结果判断去掉哪一位。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll a, vec[110]; int sze;
ll f(ll x, int p) {
ll base = 1;
while (x) {
if (x % 2) {
if (base % 3 == p) {
return base;
}
}
x >>= 1;
base <<= 1;
}
return -1;
}
void solve(ll x) {
sze = 0;
ll base = 1;
ll t = x;
while (x) {
if (x % 2) {
vec[sze++] = base;
}
x >>= 1;
base <<= 1;
}
ll A = vec[0] | vec[1] | vec[2];
ll B = t ^ vec[0];
if ((t % 3) != (vec[0] % 3)) {
B ^= vec[1];
}
printf("2 %lld %lld\n", A, B);
}
bool same(ll x) {
sze = 0;
ll base = 1;
while (x) {
if (x % 2) {
vec[sze++] = base;
}
base <<= 1;
x >>= 1;
}
for (int i = 1; i < sze; ++i) {
if ((vec[i] % 3) != (vec[i - 1] % 3)) {
return 0;
}
}
return 1;
}
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%lld", &a);
if (a % 3 == 0) {
printf("1 %lld\n", a);
} else if (same(a)) {
solve(a);
} else if (a % 3 == 1) {
ll x = f(a, 1);
if (x != -1) {
ll y = a ^ x;
x ^= f(a, 2);
printf("2 %lld %lld\n", x, y);
}
} else if (a % 3 == 2) {
ll x = f(a, 2);
if (x != -1) {
ll y = a ^ x;
x ^= f(a, 1);
printf("2 %lld %lld\n", x, y);
}
}
}
return 0;
}
I.string
题意:
给出一个字符串\(s\),定义两个子串不等价当且仅当两个子串\(a, b\)不同并且\(a\)不等于\(b\)的翻转,问从\(s\)中选出尽量多的子串,使得两两之间不等价。
思路:
考虑将\(s\)和\(s\)的翻转用'$'连接起来,那么首先两个串的子串个数相同,那么去掉了相同的部分,剩下的是等数量的不同部分\(p\)。
那么再考虑回文串带来的影响,我们发现对于回文串多减了一次,那么把所有本质不同的回文串都拉出来,假设数量为\(q\)。
那么答案就是\(\frac{p + q}{2}\)
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 500010
int n;
char s[N], t[N];
struct DA {
int t1[N], t2[N], c[N];
int sa[N];
int Rank[N];
int height[N];
int str[N];
int n, m;
void init(char *s, int m, int n) {
this->m = m;
this->n = n;
for (int i = 0; i < n; ++i) str[i] = s[i];
str[n] = 0;
}
bool cmp(int *r, int a, int b, int l) {
return r[a] == r[b] && r[a + l] == r[b + l];
}
void work() {
++n;
int i, j, p, *x = t1, *y = t2;
for (i = 0; i < m; ++i) c[i] = 0;
for (i = 0; i < n; ++i) c[x[i] = str[i]]++;
for (i = 1; i < m; ++i) c[i] += c[i - 1];
for (i = n - 1; i >= 0; --i) sa[--c[x[i]]] = i;
for (j = 1; j <= n; j <<= 1) {
p = 0;
for (i = n - j; i < n; ++i) {
y[p++] = i;
}
for (i = 0; i < n; ++i) if (sa[i] >= j) y[p++] = sa[i] - j;
for (i = 0; i < m; ++i) c[i] = 0;
for (i = 0; i < n; ++i) c[x[y[i]]]++;
for (i = 1; i < m; ++i) c[i] += c[i - 1];
for (i = n - 1; i >= 0; --i) sa[--c[x[y[i]]]] = y[i];
swap(x, y);
p = 1; x[sa[0]] = 0;
for (i = 1; i < n; ++i) {
x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1 : p++;
}
if (p >= n) break;
m = p;
}
int k = 0;
--n;
for (i = 0; i <= n; ++i) Rank[sa[i]] = i;
//build height
for (i = 0; i < n; ++i) {
if (k) --k;
j = sa[Rank[i] - 1];
while (str[i + k] == str[j + k]) ++k;
height[Rank[i]] = k;
}
}
}da;
#define ALP 26
struct PAM{
int Next[N][ALP];
int fail[N];
int cnt[N];
int num[N];
int len[N];
int s[N];
int last;
int n;
int p;
int newnode(int w){
for(int i=0;i<ALP;i++)
Next[p][i] = 0;
cnt[p] = 0;
num[p] = 0;
len[p] = w;
return p++;
}
void init(){
p = 0;
newnode(0);
newnode(-1);
last = 0;
n = 0;
s[n] = -1;
fail[0] = 1;
}
int get_fail(int x){
while(s[n-len[x]-1] != s[n]) x = fail[x];
return x;
}
bool add(int c){
bool F = 0;
c -= 'a';
s[++n] = c;
int cur = get_fail(last);
if(!Next[cur][c]){
int now = newnode(len[cur]+2);//新建节点
fail[now] = Next[get_fail(fail[cur])][c];
Next[cur][c] = now;
num[now] = num[fail[now]] + 1;
F = 1;
}
last = Next[cur][c];
cnt[last]++;
return F;
}
void count(){
for(int i=p-1;i>=0;i--)
cnt[fail[i]] += cnt[i];
}
}pam;
int main() {
while (scanf("%s", s) != EOF) {
int len = strlen(s);
n = 0;
for (int i = 0; i < len; ++i) t[n++] = s[i];
t[n++] = '$';
for (int i = len - 1; i >= 0; --i) t[n++] = s[i];
da.init(t, 128, n);
da.work();
t[n] = 0;
ll res = 0;
for (int i = 1; i <= n; ++i) {
if (t[da.sa[i]] != '$') {
if (da.sa[i] > len) {
res += n - da.sa[i] - da.height[i];
} else {
res += len - da.sa[i] - da.height[i];
}
}
}
pam.init();
for (int i = 0; i < len; ++i) pam.add(s[i]);
printf("%lld\n", (res + pam.p - 2) / 2);
}
return 0;
}
J. free
题意:
给出\(n\)个点\(m\)条边的图,求最多去掉\(m\)条边的权值从\(S \rightarrow T\)的最短路。
思路:
考虑\(f[i][j]\)表示终点为\(i\)已经去掉了\(j\)条边的代价的最短路。
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 1010
#define INF 0x3f3f3f3f
int n, m, S, T, K;
struct Graph {
struct node {
int to, nx, w;
node() {}
node (int to, int nx, int w) : to(to), nx(nx), w(w) {}
}a[N << 1];
int head[N], pos;
void init() {
pos = 0;
memset(head, -1, sizeof head);
}
void add(int u, int v, int w) {
a[pos] = node(v, head[u], w); head[u] = pos++;
a[pos] = node(u, head[v], w); head[v] = pos++;
}
}G;
#define erp(u) for (int it = G.head[u], v = G.a[it].to, w = G.a[it].w; ~it; it = G.a[it].nx, v = G.a[it].to, w = G.a[it].w)
struct node {
int u, w, k;
node() {}
node(int u, int w, int k) : u(u), w(w), k(k) {}
bool operator < (const node &other) const {
return w > other.w;
}
};
int dist[N][N], used[N][N];
void Dijkstra() {
for (int i = 1; i <= n; ++i)
for (int j = 0; j <= K; ++j) {
dist[i][j] = INF;
used[i][j] = 0;
}
dist[S][0] = 0;
priority_queue <node> pq;
pq.push(node(S, dist[S][0], 0));
while (!pq.empty()) {
int u = pq.top().u, k = pq.top().k; pq.pop();
if (used[u][k]) continue;
if (u == T) break;
used[u][k] = 1;
erp(u) if (dist[v][k] > dist[u][k] + w) {
dist[v][k] = dist[u][k] + w;
pq.push(node(v, dist[v][k], k));
}
if (k < K) {
erp(u) if (dist[v][k + 1] > dist[u][k]) {
dist[v][k + 1] = dist[u][k];
pq.push(node(v, dist[v][k + 1], k + 1));
}
}
}
int res = INF;
for (int i = 0; i <= K; ++i) res = min(res, dist[T][i]);
printf("%d\n", res);
}
int main() {
while (scanf("%d%d%d%d%d", &n, &m, &S, &T, &K) != EOF) {
G.init();
for (int i = 1, u, v, w; i <= m; ++i) {
scanf("%d%d%d", &u, &v, &w);
if (u == v) continue;
G.add(u, v, w);
}
Dijkstra();
}
return 0;
}
K. number
题意:
给出一个字符串,问里面有多少个子串它的数值含义是\(300\)的倍数,允许前导\(0\)。
思路:
考虑一个子串是\(300\)的倍数,那么只需要让它既是\(100\)的倍数,又是\(3\)的倍数即可。
- \(100\)的倍数即末尾两位是\(0\)。
- \(3\)的倍数有一个特性,即只要它的数位之和加起来是\(3\)的倍数即可,那么直接\(dp\)转移即可。
- 再特判一个\(0\)的情况
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 100010
char s[N];
ll f[N][3];
int main() {
while (scanf("%s", s + 1) != EOF) {
memset(f, 0, sizeof f);
int len = strlen(s + 1);
int cnt = 0;
ll res = 0;
for (int i = 1; i <= len; ++i) {
int num = (s[i] - '0') % 3;
++f[i][num];
for (int j = 0; j < 3; ++j) {
f[i][j] += f[i - 1][(j - num + 3) % 3];
}
cnt += (s[i] == '0');
if (i >= 2 && s[i] == '0' && s[i - 1] == '0') {
res += f[i - 2][0] + 1;
}
}
printf("%lld\n", res + cnt);
}
return 0;
}