2019牛客多校第四场
目前为止打得最爽的一场多校
A. meeting
solved at 00:58(+1)
树上有(k)个关键点,让你选择一个树上的点,使得这些关键点到这个点的最大距离最小
类似树的直径的做法,找到最远的两个关键点的距离除以二向上取整就好了
B. xor
upsolved
你有(n)个集合,每个集合有若干个数
若一个集合的任意一个子集异或和为(x),称这个集合可以表示(x)
给出(m)个询问,每次询问(l)到(r)的这些集合是否都能表示出(x)
(1<=n,m<=5e4)
目前六场多校,考了三次线性基。。。
这次是线性基求交,用线段树维护
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 10;
struct Base {
unsigned a[32];
Base() {
memset(a, 0, sizeof(a));
}
void insert(unsigned x) {
for(int i = 31; ~i; --i) {
if(x >> i & 1) {
if(!a[i]) {
a[i] = x;
return;
}
else {
x ^= a[i];
}
}
}
}
bool query(unsigned x) {
for(int i = 31; ~i; --i) {
if(x >> i & 1) {
x ^= a[i];
}
}
return x == 0;
}
}seg[N << 2];
Base intersect(const Base &a, const Base &b) {
Base all = a, c, d;
for(int i = 31; ~i; --i) d.a[i] = 1LL << i;
for(int i = 31; ~i; --i) {
if(b.a[i]) {
unsigned v = b.a[i], k = 0;
bool can = true;
for(int j = 31; ~j; --j) {
if(v >> j & 1) {
if(all.a[j]) {
v ^= all.a[j];
k ^= d.a[j];
}
else {
can = false;
all.a[j] = v;
d.a[j] = k;
break;
}
}
}
if(can) {
unsigned v = 0;
for(int j = 31; ~j; --j) {
if(k >> j & 1)
v ^= a.a[j];
}
c.insert(v);
}
}
}
return c;
}
int n, m, sz, l, r;
unsigned x;
void pushup(int rt) {
seg[rt] = intersect(seg[rt << 1], seg[rt << 1 | 1]);
}
void build(int rt, int l, int r) {
if(l == r) {
scanf("%d", &sz);
while(sz--) {
scanf("%u", &x);
seg[rt].insert(x);
}
return;
}
int mid = l + r >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
pushup(rt);
}
bool query(int rt, int l, int r, int L, int R, unsigned val) {
if(L <= l && r <= R) return seg[rt].query(val);
int mid = l + r >> 1;
if(L <= mid) if(!query(rt << 1, l, mid, L, R, val)) return false;
if(R > mid) if(!query(rt << 1 | 1, mid + 1, r, L, R, val)) return false;
return true;
}
int main() {
scanf("%d%d", &n, &m);
build(1, 1, n);
while(m--) {
scanf("%d%d%u", &l, &r, &x);
puts(query(1, 1, n, l, r, x) ? "YES" : "NO");
}
return 0;
}
C. sequence
solved at 03:25
给你两个长为(n)的数列(a, b)。 (1 le n le 3e6)
求$$maxlimits_{1le lle rle n}{min(a_{lcdots r})*sum(b_{lcdots r})}$$
对于每一个(i),我们可以用单调栈求出它作为最小值的最左端点和最右端点,若(a[i]>0),就查找(icdots r[i])这些位置中(b)数列前缀和最大的位置
,左边求后缀和,加一起就是(l[i]cdots r[i])包含位置(i)的最大子段和
如果(a[i]<=0)就求最小值
可以用线段树实现,总复杂度(O(nlogn))
题解的线性做法是笛卡尔树,并不会。。。
队友做的,代码不放了
D. triples I
solved at 01:17(+1)
输入一个数(a),输出最少的数,让这些数的或值是(a),要求输出的数均为(3)的倍数,输入保证有解
队友做的,输出的数数量一定小于等于(2),按照(a)的二进制位分类讨论
I. string
solved at 02:20(+2)
定义两个字符串(A,B)等价当且仅当(A=B)或(A=rev(B))
给定一个字符串(S),求最大的(S)的子串的集合,满足集合中没有两个元素等价
我的做法是先求(S)本质不同的子串数量,然后减去一些本质不同的但是等价的子串
用反串去匹配原串,看能有多少匹配到,注意到如果(rev(S))的子串(A)能在(S)中匹配到,那么(rev(A))也一定能匹配到,匹配到的数量减去本质不同的回文子串数量除以二就是应该减掉的值
求本质不同的字符串以及后面的匹配用(SAM)实现,统计本质不同的回文子串用(PAM)
题解的做法是把每个串和它的(rev)都算一遍,即统计(S)和(rev(S))共同的本质不同子串数量,再加上本质不同的回文子串数量(因为回文串只被算了一遍),除以二就是答案,统计子串可以用广义后缀自动机或者后缀数组实现,实现起来比我的做法更加简单
我的做法代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
struct PAM {
int fail, cnt, len;
int nxt[26];
}st[N];
long long ans, cnt_minus;
char s[N], rs[N];
int n, now, sz, cnt_rev;
void pam_init() {
memset(st, 0, sizeof(st));
st[0].fail = st[1].fail = 1;
st[1].len = -1;
sz = 1;
}
void extend(int c,int pos) {
int p = now;
while (s[pos - st[p].len - 1] != s[pos]) p = st[p].fail;
if (!st[p].nxt[c]) {
int np = ++sz, q = st[p].fail;
st[np].len = st[p].len + 2;
while(s[pos - st[q].len - 1] != s[pos]) q = st[q].fail;
st[np].fail = st[q].nxt[c];
st[p].nxt[c] = np;
}
now = st[p].nxt[c];
st[now].cnt++;
}
namespace SAM {
int len[N << 1], nxt[N << 1][26], par[N << 1], match[N << 1], c[N], pos[N << 1];
int sz, last;
int new_node(int l) {
len[sz] = l;
memset(nxt[sz], 0, sizeof(nxt[sz]));
return sz++;
}
void init() {
sz = last = 0;
par[sz] = -1;
new_node(0);
}
void add(int c) {
int p = last, np = new_node(len[last] + 1);
for(; ~p && !nxt[p][c]; p = par[p]) nxt[p][c] = np;
if(-1 == p) par[np] = 0;
else {
int q = nxt[p][c];
if(len[q] == len[p] + 1) par[np] = q;
else {
int nq = new_node(len[p] + 1);
memcpy(nxt[nq], nxt[q], sizeof(nxt[nq]));
par[nq] = par[q];
par[q] = par[np] = nq;
for(; ~p && nxt[p][c] == q; p = par[p])
nxt[p][c] = nq;
}
}
last = np;
}
void topo() {
for(int i = 0; i <= n; ++i) c[i] = 0;
for(int i = 0; i < sz; ++i) c[len[i]]++;
for(int i = 1; i <= n; ++i) c[i] += c[i - 1];
for(int i = sz - 1; ~i; --i) pos[--c[len[i]]] = i;
}
long long calc() {
long long res = 0;
for(int i = sz - 1; ~i; --i) {
int j = pos[i];
if(j == 0) continue;
if(~par[j])
match[par[j]] = max(match[par[j]], match[j]);
res += max(0, min(len[j], match[j]) - len[par[j]]);
}
return res;
}
}
int main() {
scanf("%s",s);
pam_init();
n = strlen(s);
for(int i = 0; i < n; ++i)
extend(s[i] - 'a', i);
cnt_rev = sz - 1;
SAM::init();
for(int i = 0; i < n; ++i)
SAM::add(s[i] - 'a');
for(int i = 1; i < SAM::sz; ++i) {
ans += SAM::len[i] - SAM::len[SAM::par[i]];
}
for(int i = 0; i < n; ++i) {
rs[i] = s[n - i - 1];
}
rs[n] = ' ';
int p = 0, l = 0;
for(int i = 0; i < n; ++i) {
int x = rs[i] - 'a';
while(~p && !SAM::nxt[p][x])
p = SAM::par[p], l = SAM::len[p];
if(p == -1) {
p = l = 0;
continue;
}
p = SAM::nxt[p][x], l++;
SAM::match[p] = max(SAM::match[p], l);
}
SAM::topo();
cnt_minus = SAM::calc();
cnt_minus = (cnt_minus - cnt_rev) / 2;
printf("%lld
", ans - cnt_minus);
return 0;
}
J. free
solved at 00:43
一个无向图,经过一条边需要支付一个代价,你可以让最多(k)条边的代价变为(0),求从起点到终点的最小代价((1le n,mle1e3, 0le kle m))
签到题,队友做的,拆点跑最短路
K. number
solved at 00:13
问一个数字字符串有多少子串是(300)的倍数
签到题,线性扫一下统计前缀和计算答案就好了