lrz数据结构水题赛题解
lrz的数据结构比赛
似乎没人打(⊙︿⊙)
但是题解不能咕QAQ
T1 膜犇犇
这题真的是道签到题W( ̄_ ̄)W
让你每次求一个区间\([l,r]\)里包含数字种数最多的子段的最短长度
\(O(n^2m)\)的暴力是很好写的,每次枚举所有子段
然后优化一下,考虑二分这个最短长度,这样复杂度降到了\(O(nlognm)\),可以拿到\(50\)分的好成绩
这样子就没法再优化了,那换种思路,我们想想这个子段的性质
首先最坏情况是这个区间所有的数都不一样,那答案就是整个区间
然后如果这个区间有一样的数,我们就往里缩区间(这里的缩区间并不是贪心的缩,贪心的是错的)
先把\([l,r]\)这个区间数字种数求出来,这个很好求,值域很小,连离散化都不用,开个桶就可以了,当然你想写主席树我也不拦你
我们用双指针,\(l\)指针指向区间左端点,往右移\(r\)指针,当我们移到一个点时这个区间的数字种数等于询问的区间的数字种数,那么就可以更新答案,然后再往右移\(l\)指针
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define N 5000
using namespace std;
int n,m,a[N + 5],cnt[N + 5],num;
int main()
{
scanf("%d",&n);
for (int i = 1;i <= n;i++)
scanf("%d",&a[i]);
scanf("%d",&m);
int opt,l,r,ll,rr,num,ans,now;
for (int i = 1;i <= m;i++)
{
scanf("%d%d",&l,&r);
memset(cnt,0,sizeof(cnt));
num = 0;
ans = N + 5;
now = 0;
for (int j = l;j <= r;j++)
{
cnt[a[j]]++;
if (cnt[a[j]] == 1)
num++;
}
memset(cnt,0,sizeof(cnt));
ll = l;
rr = l;
while (ll <= r)
{
while (now < num && rr <= r)
{
cnt[a[rr]]++;
if (cnt[a[rr]] == 1)
now++;
rr++;
}
if (now == num)
ans = min(ans,rr - ll);
cnt[a[ll]]--;
if (cnt[a[ll]] == 0)
now--;
ll++;
}
printf("%d\n",ans);
}
return 0;
}
T2 强迫症
维护一个数据结构支持:区间加,区间求和,区间求\(max\),区间赋值\(max\)
这个题其实是吉司机线段树的板子题
先说下其他的能拿分的做法吧
用线段树乱搞,维护一个区间最大值和最小值,和要修改的值比较一下跳过区间,在随机数据下复杂的显得非常优秀,可以拿\(20\)分
分块,做法还蛮多的,但是\(O(n\sqrt{n})\)似乎有点困难(被Juan_feng秒了),可以拿\(40\)分
我们注意到前三个操作用线段树是很好维护的,瓶颈在于第四个操作
那么考虑维护一个区间最小值\(mi\),区间次小值\(cmi\),区间最小值的出现次数\(t\),对于和一个数\(x\)取\(max\)有三种可能:
\(x\le mi\),说明对这个区间没有贡献
\(mi<x<cmi\),说明只有\(mi\)被修改,拿\(t\)和\(x - mi\)修改区间和,更新区间最大值即可
\(x\le cmi\),这样子没法直接求,那么我们继续递归它的左右儿子,最终一定会到前两种情况并停止
注意下先下放加法标记再下放修改标记就好了
复杂度\(O(nlog^2n)\),证明的话需要用到势能和均摊,蒟蒻太菜不会证,还是去看吉司机的证明吧(吉司机证的\(O(nlogn)\)是假的)
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define zrt k << 1
#define yrt k << 1 | 1
#define N 300000
#define INF 9999999999
using namespace std;
int n,m,a[N + 5];
struct node
{
int t;
long long su,cmi,at,mi,ma,tag;
node ()
{
ma = at = mi = t = su = tag = 0;
cmi = INF;
}
};
struct Seg
{
node s[N * 4 + 5];
node upd(node x,node y)
{
node k;
k.su = x.su + y.su;
k.ma = max(x.ma,y.ma);
if (x.mi == y.mi)
{
k.mi = x.mi;
k.t = x.t + y.t;
k.cmi = min(x.cmi,y.cmi);
}
else
if (x.mi < y.mi)
{
k.mi = x.mi;
k.t = x.t;
k.cmi = min(x.cmi,y.mi);
}
else
{
k.mi = y.mi;
k.t = y.t;
k.cmi = min(x.mi,y.cmi);
}
return k;
}
void build(int k,int l,int r)
{
if (l == r)
{
s[k].mi = s[k].ma = s[k].su = a[l];
s[k].cmi = INF;
s[k].t = 1;
return;
}
int mid = l + r >> 1;
build(zrt,l,mid);
build(yrt,mid + 1,r);
s[k] = upd(s[zrt],s[yrt]);
}
bool cha(int k,int l,int r,long long z)
{
if (z <= s[k].mi)
return 1;
if (z <= s[k].cmi)
{
s[k].su += (long long)s[k].t * (z - s[k].mi);
s[k].mi = z;
s[k].ma = max(s[k].ma,z);
s[k].tag = max(z,s[k].tag);
return 1;
}
return 0;
}
void jia(int k,int l,int r,long long z)
{
s[k].su += z * (long long)(r - l + 1);
s[k].ma += z;
s[k].mi += z;
s[k].cmi += z;
s[k].at += z;
if (s[k].tag)
s[k].tag += z;
}
void pushdown(int k,int l,int r,int mid)
{
if (s[k].at)
{
jia(zrt,l,mid,s[k].at);
jia(yrt,mid + 1,r,s[k].at);
s[k].at = 0;
}
if (s[k].tag)
{
cha(zrt,l,mid,s[k].tag);
cha(yrt,mid + 1,r,s[k].tag);
s[k].tag = 0;
}
}
void change(int k,int l,int r,int x,int y,int z)
{
if (l >= x && r <= y)
{
if (cha(k,l,r,(long long)z))
return;
int mid = l + r >> 1;
pushdown(k,l,r,mid);
change(zrt,l,mid,x,y,z);
change(yrt,mid + 1,r,x,y,z);
s[k] = upd(s[zrt],s[yrt]);
return;
}
int mid = l + r >> 1;
pushdown(k,l,r,mid);
if (x > mid)
change(yrt,mid + 1,r,x,y,z);
else
if (y <= mid)
change(zrt,l,mid,x,y,z);
else
change(zrt,l,mid,x,y,z),change(yrt,mid + 1,r,x,y,z);
s[k] = upd(s[zrt],s[yrt]);
}
void add(int k,int l,int r,int x,int y,int z)
{
if (l >= x && r <= y)
{
jia(k,l,r,(long long)z);
return;
}
int mid = l + r >> 1;
pushdown(k,l,r,mid);
if (x > mid)
add(yrt,mid + 1,r,x,y,z);
else
if (y <= mid)
add(zrt,l,mid,x,y,z);
else
add(zrt,l,mid,x,y,z),add(yrt,mid + 1,r,x,y,z);
s[k] = upd(s[zrt],s[yrt]);
}
node query(int k,int l,int r,int x,int y)
{
if (l >= x && r <= y)
return s[k];
int mid = l + r >> 1;
pushdown(k,l,r,mid);
if (x > mid)
return query(yrt,mid + 1,r,x,y);
else
if (y <= mid)
return query(zrt,l,mid,x,y);
else
return upd(query(zrt,l,mid,x,y),query(yrt,mid + 1,r,x,y));
}
}tree;
int main()
{
scanf("%d",&n);
for (int i = 1;i <= n;i++)
scanf("%d",&a[i]);
tree.build(1,1,n);
scanf("%d",&m);
int opt,l,r,z;
for (int i = 1;i <= m;i++)
{
scanf("%d%d%d",&opt,&l,&r);
if (opt == 1)
{
scanf("%d",&z);
tree.add(1,1,n,l,r,z);
}
else
if (opt == 2)
{
scanf("%d",&z);
tree.change(1,1,n,l,r,z);
}
else
if (opt == 3)
printf("%lld\n",tree.query(1,1,n,l,r).su);
else
printf("%lld\n",tree.query(1,1,n,l,r).ma);
}
return 0;
}
T3 机房
题意:有两个数组\(A,B\),每次可以在\(A\)上区间加,在\(B\)上加对应\(a_i\times x\),询问\(B\)的区间和
直接暴力,复杂度\(O(nm)\),有\(20\)分
其实这个东西仍然是可以用线段树维护的,分别维护\(A,B\)的区间和,那么维护的就是\(\sum b_i+w\times\sum a_i + \Delta\),\(\Delta\)是对\(A\)区间加之后产生的影响
对于每次区间对\(B\)加\(w\),直接更新\(w\times\sum a_i\),而对\(A\)区间加\(t\),带入式子就是\(t\times w\),在线段树上多维护几个标记就好了
复杂度\(O(nlogn)\),常数很大
当然还有一种线段树维护\(3\times 3\)矩阵的操作,这样子常数更大,过不去
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define N 300000
#define zrt k << 1
#define yrt k << 1 | 1
using namespace std;
int n,m,p = 1e9 + 7,a[N + 5],t[N + 5];
struct node
{
int ft,at,nt,su,sut;
node ()
{
ft = at = nt = su = sut = 0;
}
};
struct Seg
{
node s[N *4 + 5];
node upd(node x,node y)
{
x.ft = x.at = x.nt = 0;
x.su = (x.su + y.su) % p;
x.sut = (x.sut + y.sut) % p;
return x;
}
void build(int k,int l,int r)
{
if (l == r)
{
s[k].su = a[l];
s[k].sut = t[l];
return;
}
int mid = l + r >> 1;
build(zrt,l,mid);
build(yrt,mid + 1,r);
s[k] = upd(s[zrt],s[yrt]);
}
void cha(int k,int l,int r,int t)
{
s[k].sut = (s[k].sut + 1LL * t * (r - l + 1)) % p;
s[k].at = (s[k].at + t) % p;
}
void jia(int k,int l,int r,int z)
{
s[k].su = (s[k].su + s[k].sut * 1LL * z) % p;
s[k].ft = (s[k].ft + z) % p;
s[k].nt = (s[k].nt + s[k].at * 1LL * z) % p;
}
void jiaa(int k,int l,int r,int z)
{
s[k].su = (s[k].su + (r - l + 1) * 1LL * z) % p;
s[k].nt = (s[k].nt + z) % p;
}
void pushdown(int k,int l,int r,int mid)
{
if (s[k].ft)
{
jia(zrt,l,mid,s[k].ft);
jia(yrt,mid + 1,r,s[k].ft);
s[k].ft = 0;
}
if (s[k].nt)
{
jiaa(zrt,l,mid,s[k].nt);
jiaa(yrt,mid + 1,r,s[k].nt);
s[k].nt = 0;
}
if (s[k].at)
{
cha(zrt,l,mid,s[k].at);
cha(yrt,mid + 1,r,s[k].at);
s[k].at = 0;
}
}
void change(int k,int l,int r,int x,int y,int t)
{
if (l >= x && r <= y)
{
cha(k,l,r,t);
return;
}
int mid = l + r >> 1;
pushdown(k,l,r,mid);
if (x > mid)
change(yrt,mid + 1,r,x,y,t);
else
if (y <= mid)
change(zrt,l,mid,x,y,t);
else
change(zrt,l,mid,x,y,t),change(yrt,mid + 1,r,x,y,t);
s[k] = upd(s[zrt],s[yrt]);
}
void add(int k,int l,int r,int x,int y,int z)
{
if (l >= x && r <= y)
{
jia(k,l,r,z);
return;
}
int mid = l + r >> 1;
pushdown(k,l,r,mid);
if (x > mid)
add(yrt,mid + 1,r,x,y,z);
else
if (y <= mid)
add(zrt,l,mid,x,y,z);
else
add(zrt,l,mid,x,y,z),add(yrt,mid + 1,r,x,y,z);
s[k] = upd(s[zrt],s[yrt]);
}
node query(int k,int l,int r,int x,int y)
{
if (l >= x && r <= y)
return s[k];
int mid = l + r >> 1;
pushdown(k,l,r,mid);
if (x > mid)
return query(yrt,mid + 1,r,x,y);
else
if (y <= mid)
return query(zrt,l,mid,x,y);
else
return upd(query(zrt,l,mid,x,y),query(yrt,mid + 1,r,x,y));
}
}tree;
int main()
{
scanf("%d",&n);
for (int i = 1;i <= n;i++)
scanf("%d",&t[i]);
for (int i = 1;i <= n;i++)
scanf("%d",&a[i]);
tree.build(1,1,n);
scanf("%d",&m);
int opt,l,r,z;
for (int i = 1;i <= m;i++)
{
scanf("%d%d%d",&opt,&l,&r);
if (opt == 1)
{
scanf("%d",&z);
tree.change(1,1,n,l,r,z);
}
else
if (opt == 2)
{
scanf("%d",&z);
tree.change(1,1,n,l,r,-z);
}
else
if (opt == 3)
{
scanf("%d",&z);
tree.add(1,1,n,l,r,z);
}
else
printf("%d\n",(tree.query(1,1,n,l,r).su % p + p) % p);
}
return 0;
}
T4 小\(e\)的考验
这题本来是想当签到题出的说
我们思考一下,其实就是每次问你这个区间不能被\(x\)整除的数是否超过\(k\)个
-
我们注意到第一个子任务的\(k\)很小,那么我们可以大力线段树,维护一个区间\(gcd\),如果这个\(x|gcd\)那么继续往里走,走到叶子节点就统计一下,复杂度\(O(knlogn)\)
-
而第二个子任务的特点是值域小,那么我们可以去寻找一种值域做法
考虑根号分治,对序列分块,对于每个询问\(x\),如果\(x<=\sqrt{N},N\)是值域,那么直接查,否则暴力枚举其倍数统计答案
对于小于\(\sqrt{N}\)的询问,我们预处理出来\(c_{ij}\)表示第\(i\)个块能被\(j\)整除的个数,然后整块直接查\(c\)就好了
对于大于\(\sqrt{N}\)的询问,预处理出\(f_{ij}\)表示前\(i\)个块中\(j\)的个数,对于所有整块,一边枚举倍数,一边前缀和一减统计答案就可以了
单点修改直接修改\(c,f\),复杂度\(O(n\sqrt{n})\)
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define N 500000
#define M 500000
#define MI 100000
#define GN 316
#define zrt k << 1
#define yrt k << 1 | 1
using namespace std;
int n,a[N + 5],m,cnt,T,bs,blo[MI + 5],L[MI + 5],R[MI + 5],f[GN + 5][MI + 5],c[GN + 5][GN + 5];
struct node
{
int g;
};
int gcd(int a,int b)
{
if (!b)
return a;
return gcd(b,a%b);
}
struct Seg
{
node s[N * 4 + 5];
node upd(node x,node y)
{
node k;
k.g = gcd(x.g,y.g);
return k;
}
void build(int k,int l,int r)
{
if (l == r)
{
s[k].g = a[l];
return;
}
int mid = l + r >> 1;
build(zrt,l,mid);
build(yrt,mid + 1,r);
s[k] = upd(s[zrt],s[yrt]);
}
void change(int k,int l,int r,int x,int z)
{
if (l == r)
{
s[k].g = z;
return;
}
int mid = l + r >> 1;
if (x <= mid)
change(zrt,l,mid,x,z);
else
change(yrt,mid + 1,r,x,z);
s[k] = upd(s[zrt],s[yrt]);
}
void query(int k,int l,int r,int x,int y,int z,int t)
{
if (cnt > t)
return;
if (l >= x && r <= y && s[k].g % z == 0)
return;
if (l == r)
{
cnt++;
return;
}
int mid = l + r >> 1;
if (x > mid)
query(yrt,mid + 1,r,x,y,z,t);
else
if (y <= mid)
query(zrt,l,mid,x,y,z,t);
else
query(zrt,l,mid,x,y,z,t),query(yrt,mid + 1,r,x,y,z,t);
}
}tree;
int query(int l,int r,int z)
{
if (blo[r] - blo[l] <= 1)
{
int ans = 0;
for (int i = l;i <= r;i++)
if (a[i] % z == 0)
ans++;
return r - l + 1 - ans;
}
else
{
int ans = 0;
for (int i = l;i <= R[blo[l]];i++)
if (a[i] % z == 0)
ans++;
for (int i = L[blo[r]];i <= r;i++)
if (a[i] % z == 0)
ans++;
if (z > GN)
for (int i = z;i <= MI;i += z)
ans += f[blo[r] - 1][i] - f[blo[l]][i];
else
for (int i = blo[l] + 1;i < blo[r];i++)
ans += c[i][z];
return r - l + 1 - ans;
}
}
void change(int l,int z)
{
for(int i = blo[l];i <= blo[n];i++)
f[i][a[l]]--,f[i][z]++;
for(int i = 1;i <= GN;i++)
{
if (a[l] % i == 0)
c[blo[l]][i]--;
if (z % i == 0)
c[blo[l]][i]++;
}
a[l] = z;
}
int main()
{
scanf("%d",&T);
if (T == 1) //subtask 1
{
scanf("%d",&n);
for (int i = 1;i <= n;i++)
scanf("%d",&a[i]);
tree.build(1,1,n);
scanf("%d",&m);
int opt,l,r,z,t;
for (int i = 1;i <= m;i++)
{
scanf("%d%d%d",&opt,&l,&r);
if (opt == 2)
{
cnt = 0;
scanf("%d%d",&t,&z);
tree.query(1,1,n,l,r,z,t);
if (cnt <= t)
printf("YES\n");
else
printf("NO\n");
}
else
tree.change(1,1,n,l,r);
}
}
else //subtask 2
{
scanf("%d",&n);
bs = sqrt(n);
for (int i = 1;i <= n;i++)
scanf("%d",&a[i]);
for (int i = 1;i <= MI;i++)
{
blo[i] = (i - 1) / bs + 1;
if (!L[blo[i]])
L[blo[i]] = i;
R[blo[i]] = i;
}
for (int i = 1;i <= blo[n];i++)
{
for (int j = 1;j <= MI;j++)
f[i][j] = f[i - 1][j];
for (int j = L[i];j <= R[i];j++)
{
f[i][a[j]]++;
for (int k = 1;k <= GN;k++)
if (a[j] % k == 0)
c[i][k]++;
}
}
scanf("%d",&m);
int opt,l,r,z,t;
for (int i = 1;i <= m;i++)
{
scanf("%d%d%d",&opt,&l,&r);
if (opt == 2)
{
scanf("%d%d",&t,&z);
if (query(l,r,z) <= t)
printf("YES\n");
else
printf("NO\n");
}
else
{
if (a[l] == r)
continue;
change(l,r);
}
}
}
return 0;
}
代码写的有点丑= =