BZOJ Lydsy5月月赛 ADG题解

时间:2023-03-09 16:30:16
BZOJ  Lydsy5月月赛   ADG题解

题目链接

BZOJ5月月赛

题解

好弱啊QAQ只写出三题

A

判断多干个数乘积是否是某个数的倍数有很多方法,比较常用的是取模,但这里并不适用,因为模数不定

会发现数都比较小,所以我们可以考虑分解质因子,查找一下区间各个质因子数是否符合要求

用主席树维护即可

由于\(10^5\)以内不同质因子数最多的也就是\(6\)个,预处理一下质因子,可以看做一个常数

复杂度是\(O(n\sqrt{n} + nlogn)\)的

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define mp(a,b) make_pair<int,int>(a,b)
#define cls(s) memset(s,0,sizeof(s))
#define cp pair<int,int>
#define LL long long int
using namespace std;
const int maxn = 100005,maxm = 10000005,N = 100000,INF = 1000000000;
inline int read(){
int out = 0,flag = 1; char c = getchar();
while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
return out * flag;
}
int fac[maxn][20],num[maxn][20],fi[maxn],M;
int p[maxn],pi,isn[maxn];
void init(){
for (int i = 2; i <= N; i++){
if (!isn[i]) p[++pi] = i;
for (int j = 1; j <= pi && i * p[j] <= N; j++){
isn[i * p[j]] = true;
if (i % p[j] == 0) break;
}
}
for (int i = 2; i <= N; i++){
int x = i,t;
for (int j = 1; j <= pi && p[j] * p[j] <= x; j++){
if (x % p[j] == 0){
t = ++fi[i];
fac[i][t] = j;
while (x % p[j] == 0) num[i][t]++,x /= p[j];
}
}
if (x - 1){
fac[i][++fi[i]] = lower_bound(p + 1,p + 1 + pi,x) - p;
num[i][fi[i]] = 1;
}
}
M = pi;
}
int sum[maxm],ls[maxm],rs[maxm],rt[maxn],A[maxn],n,m,cnt;
void add(int& u,int v,int l,int r,int pos,int w){
sum[u = ++cnt] = sum[v] + w;
ls[u] = ls[v]; rs[u] = rs[v];
if (l == r) return;
int mid = l + r >> 1;
if (mid >= pos) add(ls[u],ls[v],l,mid,pos,w);
else add(rs[u],rs[v],mid + 1,r,pos,w);
}
int query(int u,int v,int l,int r,int pos){
if (!u) return 0;
if (l == r) return sum[u] - sum[v];
int mid = l + r >> 1;
if (mid >= pos) return query(ls[u],ls[v],l,mid,pos);
return query(rs[u],rs[v],mid + 1,r,pos);
}
int main(){
init();
int T = read(),x,l,r,flag;
while (T--){
cnt = 0;
n = read(); m = read();
REP(i,n){
x = A[i] = read(); rt[i] = rt[i - 1];
for (int j = 1; j <= fi[x]; j++)
add(rt[i],rt[i],1,M,fac[x][j],num[x][j]);
}
while (m--){
l = read(); r = read(); x = read(); flag = true;
for (int i = 1; i <= fi[x]; i++)
if (query(rt[r],rt[l - 1],1,M,fac[x][i]) < num[x][i]){
flag = false; break;
}
if (!flag) puts("No");
else puts("Yes");
}
}
return 0;
}

D

D是树上两点间的询问,考虑使用树上权值主席树

由于只考虑奇偶性,所以我们覆给每个位置\(1\)或\(2\),修改就翻转一下

如何查询?

拿出\(u\)和\(v\)所对应的两棵树,我们需要快速合并树上信息得出第一个偶数位置

容易知道当两个位置奇偶性不同时相加起来才是奇数,所以我们只需要找到两棵树第一个相同的位置

权值不同不好比较,那我们就再维护一个值,与原来的值相反,这样转化成了查找第一个不同的位置

我们只需快速比较两个区间是否相同,使用\(hash\)即可

两个树实际上对应两条到根的链,相交的部分除了\(lca\)处都应减去,由于只考虑奇偶性,所以对答案没有影响,所以只用把\(lca\)处的值修改一下即可进行比较

复杂度\(O(nlogn)\),还要优化一下常数,比如只建一次树,\(RMQ\)求\(lca\)等

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#define Redge(u) for (register int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (register int i = 1; i <= (n); i++)
#define mp(a,b) make_pair<int,int>(a,b)
#define cls(s) memset(s,0,sizeof(s))
#define cp pair<int,int>
#define LL long long int
#define ULL unsigned long long int
using namespace std;
const int maxn = 200005,maxm = 13000005,INF = 1000000000;
inline int read(){
int out = 0,flag = 1; char c = getchar();
while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
return out * flag;
}
int h[maxn],ne = 1;
struct EDGE{int to,nxt;}ed[maxn << 1];
inline void build(int u,int v){
ed[++ne] = (EDGE){v,h[u]}; h[u] = ne;
ed[++ne] = (EDGE){u,h[v]}; h[v] = ne;
}
ULL pw[maxn],val[maxm],vv[maxm];
int A[maxn],fa[maxn],dep[maxn],dfn[maxn],mn[maxn << 1][19],Log[maxn << 1],bin[50],tot;
int rt[maxn],ls[maxm],rs[maxm],cnt,n,m,N = 200001,typ,pos,V;
inline void upd(int u,int l,int r){
val[u] = val[ls[u]] * pw[r - (l + r >> 1)] + val[rs[u]];
vv[u] = vv[ls[u]] * pw[r - (l + r >> 1)] + vv[rs[u]];
}
void build(int& u,int l,int r){
u = ++cnt;
if (l == r){val[u] = 1; vv[u] = 2; return;}
int mid = l + r >> 1;
build(ls[u],l,mid);
build(rs[u],mid + 1,r);
upd(u,l,r);
}
void modify(int& u,int v,int l,int r){
u = ++cnt; ls[u] = ls[v]; rs[u] = rs[v];
if (l == r){val[u] = val[v] == 1 ? 2 : 1; vv[u] = vv[v] == 1 ? 2 : 1; return;}
int mid = l + r >> 1;
if (mid >= pos) modify(ls[u],ls[v],l,mid);
else modify(rs[u],rs[v],mid + 1,r);
upd(u,l,r);
}
int query(int u,int v,int l,int r){
if (l == r) return l;
int mid = l + r >> 1;
ULL t1 = val[ls[u]],t2 = vv[ls[v]];
if (pos >= l && pos <= mid){
if (V == 1) t1 += pw[mid - pos];
else t1 -= pw[mid - pos];
}
if (t1 != t2) return query(ls[u],ls[v],l,mid);
return query(rs[u],rs[v],mid + 1,r);
}
inline int ask(int u){
int l = 1,r = N,mid;
while (l < r){
mid = l + r >> 1;
if (mid >= pos) r = mid,u = ls[u];
else l = mid + 1,u = rs[u];
}
return val[u];
}
void dfs(int u){
pos = A[u]; mn[++tot][0] = u; dfn[u] = tot;
modify(rt[u],rt[fa[u]],1,N);
Redge(u) if ((to = ed[k].to) != fa[u]){
fa[to] = u; dep[to] = dep[u] + 1;
dfs(to);
mn[++tot][0] = u;
}
}
inline int lca(int u,int v){
int l = dfn[u],r = dfn[v];
if (l > r) swap(l,r);
int t = Log[r - l + 1];
return dep[mn[l][t]] < dep[mn[r - bin[t] + 1][t]] ? mn[l][t] : mn[r - bin[t] + 1][t];
}
/*void print(int u,int l,int r){
if (l == r){
printf("%lld ",val[u]);
return;
}
int mid = l + r >> 1;
print(ls[u],l,mid);
if (6 > mid)print(rs[u],mid + 1,r);
}*/
int main(){
bin[0] = 1; for (int i = 1; i <= 25; i++) bin[i] = bin[i - 1] << 1;
Log[0] = -1; for (register int i = 1; i < (maxn << 1); i++) Log[i] = Log[i >> 1] + 1;
pw[0] = 1; for (register int i = 1; i < maxn; i++) pw[i] = pw[i - 1] * 5;
build(rt[0],1,N);
int tmp = cnt;
int T = read();
while (T--){
n = read(); m = read();
REP(i,n) h[i] = 0;
tot = 0; cnt = tmp; ne = 1;
REP(i,n) A[i] = read();
for (register int i = 1; i < n; i++) build(read(),read());
dfs(1);
for (register int j = 1; j <= 18; j++)
for (register int i = 1; i <= tot; i++){
if (i + bin[j] - 1 > tot) break;
mn[i][j] = dep[mn[i][j - 1]] < dep[mn[i + bin[j - 1]][j - 1]] ? mn[i][j - 1] : mn[i + bin[j - 1]][j - 1];
}
register int u,v,o;
while (m--){
u = read(); v = read(); o = lca(u,v);
//printf("lca(%d,%d) = %d\n",u,v,o);
//print(rt[u],1,N); puts("");
//print(rt[v],1,N); puts("");
pos = A[o];
V = ask(rt[u]);
printf("%d\n",query(rt[u],rt[v],1,N));
}
}
return 0;
}

G

一眼看过去以为是裸的后缀数组,仔细一看原来串那么长

但是思想是相通的

先讲讲如果是一般的串怎么做

我们只需要写出一个\(O(1)\)的\(cmp\)函数即可进行排序

我们知道比较两个字符串实质是比较第一个不同的字符【串长不够特殊考虑】

所以我们只需要\(O(1)\)求出两个位置的\(lcp\)即可

这是后缀数组拿手的地方

然而换到了这道题,我们同样可以通过求\(lcp\)来进行比较,但不是用后缀数组

我们先压缩一下,保证任意相邻两段字符不同

我们发现\(m\)很小,可以\(O(m^2)\)dp求出任意两段开头位置开始的\(lcp\),记为\(f[i][j]\)

我们对于任意两个串,对于段开头前面的部分我们可以直接判断是否相同,如果它们字符都不一样当然不同,如果字符一样就看到该段末尾的距离,由于相邻段字符是不一样的,如果距离不同,那么到段的结尾后自然就不同了,如果距离还相同,那么就可以加上\(f[i][j]\),就是\(lcp\)了

综上可以\(O(nlogn)\)预处理串的位置,\(O(m^2)\)dp,\(O(nlog^2n)\)排序

总复杂度\(O(nlog^2n + m^2)\)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define mp(a,b) make_pair<int,int>(a,b)
#define cls(s) memset(s,0,sizeof(s))
#define cp pair<int,int>
#define LL long long int
using namespace std;
const int maxn = 300005,maxm = 2005,INF = 1000000000;
inline LL read(){
LL out = 0,flag = 1; char c = getchar();
while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
return out * flag;
}
char s[maxm];
int n,m,id[maxn];
LL num[maxm],ll[maxn],len[maxn],l[maxn],lcp[maxm][maxm];
bool cmp(const int& x,const int& y){
LL u = l[x],v = l[y],L;
if (s[u] != s[v]) return s[u] < s[v];
else {
LL l1 = num[u] - ll[x] + 1,l2 = num[v] - ll[y] + 1;
if (l1 != l2) L = min(l1,l2);
else L = l1 + lcp[u + 1][v + 1];
}
L = min(L,min(len[x],len[y]));
//printf("%d-%d lcp = %lld\n",x,y,L);
if (L == len[x] && L == len[y]) return x < y;
if (L == len[x]) return true;
if (L == len[y]) return false;
int p1 = lower_bound(num + 1,num + 1 + m,ll[x] + L) - num;
int p2 = lower_bound(num + 1,num + 1 + m,ll[y] + L) - num;
return s[p1] < s[p2];
}
int main(){
int T = read(); char c;
LL L,R;
while (T--){
n = read(); m = read();
for (int i = 1; i <= m; i++){
c = getchar(); while (!isalpha(c)) c = getchar();
num[i] = read();
if (i > 1 && c == s[i - 1]){
num[i - 1] += num[i];
i--; m--;
}
else s[i] = c;
}
REP(i,m) lcp[i][m + 1] = lcp[m + 1][i] = 0;
for (int i = m; i; i--)
for (int j = m; j; j--){
if (s[i] != s[j]) lcp[i][j] = 0;
else if (num[i] == num[j]) lcp[i][j] = num[i] + lcp[i + 1][j + 1];
else lcp[i][j] = min(num[i],num[j]);
//printf("lcp(%d,%d) = %lld\n",i,j,lcp[i][j]);
}
for (int i = 1; i <= m; i++) num[i] += num[i - 1];
for (int i = 1; i <= n; i++){
L = read(); R = read(); id[i] = i;
len[i] = R - L + 1;
l[i] = lower_bound(num + 1,num + 1 + m,L) - num;
ll[i] = L;
}
sort(id + 1,id + 1 + n,cmp);
for (int i = 1; i <= n; i++){
printf("%d",id[i]);
if (i < n) putchar(' ');
}
if (T) puts("");
}
return 0;
}