YNOI 的大手
WZH 大战 YNOI ——我真的会数据结构吗(一)
YNOI 的题目非常毒瘤……数据结构天花板。
——我。
T1 :[Ynoi Easy Round 2016] 掉进兔子洞
题目链接: [Ynoi Easy Round 2016] 掉进兔子洞 。
题目内容:
给定一个序列,每次给三个区间 [l1,r1],[l2,r2],[l3,r3] ,把三个区间中同时出现的数一个一个删掉,问最后三个区间剩下的数的个数和,询问独立。
序列长度 n≤105 ,询问次数 m≤105 。
时间限制 3s ,空间限制 500MB 。
第一眼看这个题目就可以感觉到我们之前学的知识,像线段树,是很难发挥作用的。
而这个题目主要是区间问题,那么第一想法自然是使用莫队暴力跑三个区间,复杂度 O(n611) ,显然会炸。
那么此时就只能想办法把三个区间拆开了。
首先,原问题等价于求三个区间有多少个公共元素,如果我能求出每个区间每个元素是否出现过,然后再合并就行了。
看到合并,便可直接想到 bitset 维护。
但是这样就有了第一个问题: 如果有多个相同元素, bitset 怎么处理?
这就很考验离散化的基本功了。离散化的时候去重是为了省去重复元素所占空间,那么我只要不去重,就自然可以为某个元素出现一次,出现两次……预留出位置了。
但是还有一个问题,这样做会 MLE 。
为了解决这个问题,我们不妨看一下空间都在哪里消耗了。这自然是对于每个询问,我们为了这个而开的 bitset 。那么如果我能少开一点 bitset ,即,一次莫队少处理几次询问,多跑几次莫队,就可以不 MLE 了。
具体的,若莫队一次处理 km 个询问,则一共要跑 k 趟莫队,每趟莫队的时间复杂度为 nkm=knkm ,空间复杂度为 kwnm ,那么总时间复杂度就是 nkm ,空间复杂度为 kwnm 。代入 n=m=105 可得 k=3 或左右即可。
见代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include <bits/stdc++.h> using namespace std; const int N=100010,B=350; int n,m,a[N],am[N],book[N],b[N],cnt[N]; int get(int x){ return lower_bound(am+1,am+1+n,x)-am; } struct node{ int l,r,id; }q[N]; bool operator<(node x,node y){ return x.l/B==y.l/B?x.r/B<y.r/B:x.l/B<y.l/B; } bitset<N>bt,ans[N/3]; void add(int x){ bt[x+b[x]]=1; b[x]++; } void del(int x){ b[x]--; bt[x+b[x]]=0; } void solve(int xx){ int mm=0; for(int i=1;i<=xx;i++){ cnt[i]=0; for(int j=1;j<=3;j++){ mm++; cin>>q[mm].l>>q[mm].r; q[mm].id=i; cnt[i]+=q[mm].r-q[mm].l+1; } } sort(q+1,q+1+mm); int l=1,r=0; for(int i=1;i<=mm;i++){ while(l>q[i].l)l--,add(a[l]); while(r<q[i].r)r++,add(a[r]); while(l<q[i].l)del(a[l]),l++; while(r>q[i].r)del(a[r]),r--; if(book[q[i].id])ans[q[i].id]&=bt; else book[q[i].id]=1,ans[q[i].id]=bt; } for(int i=1;i<=xx;i++)cout<<cnt[i]-3*(int)(ans[i].count())<<"\n",ans[i].reset(),book[i]=0; for(int i=1;i<=n;i++)b[i]=0;bt.reset(); } int main(){ cin>>n>>m; for(int i=1;i<=n;i++)cin>>a[i],am[i]=a[i]; sort(am+1,am+1+n); for(int i=1;i<=n;i++)a[i]=get(a[i]); solve(m/3);solve(m/3);solve(m-m/3*2); return 0; }
|
T2 : [Ynoi Easy Round 2017] 由乃的玉米田
题目链接: [Ynoi Easy Round 2017] 由乃的玉米田 。
题目内容:
给定一个序列,每次判断区间 [l,r] 内是否有存在两个数 a,b 使得 a+b=x 或 a−b=x 或 ab=x 或 a=bx 。
序列长度 n≤105 且对于序列每个元素 ai 满足 0≤ai≤105 ,操作次数 m≤105 。
时间限制 1s ,空间限制 128MB 。
做完 T1 后来看这题的。
这种存在性问题和区间问题自然也是莫队和 bitset 了。
那么自然的,设 bitset 为 B ,那么对于 ab=x 的问题就很简单了,只需要 O(n) 的枚举因数就行了。
对于 a−b=x ,只需要判断 (B&(B<<x)).any() 就行了。
同样的,对于 a+b=x ,只需要判断 (b&(rev>>N-q[i].x)).any() 就行了,其中 rev 为将 B 反过来的结果, N 为 bitset 大小。
好了,现在就已经可以 AC 双倍经验 小清新人渣的本愿 了。
接下来是最后一个 a=bx 的询问了,如果 x≥105 ,那么完全可以在 O(n) 的时间内处理出来。如果 x<105 ,我们考虑把这类询问单独处理(因为这类询问单次 add 和 del 有着非 O(1) 的复杂度)。
不难发现,当 x=1 是一定有解,而当 x=2 的时候最浪费时间。而如果像 x=2 这种询问我们在开一个数组记录,那么在莫队移动是这种东西复杂度却是 O(x) 的。
这种巨大的差异不难让人想到根号分治。
具体的,设一个阈值 K ,当 x>K 时,仍然使用 x>105 的方法;当 x≤K 时,我们单独处理,在莫队中开一个数组记录答案。可以发现,如果 n,m 同阶,这样做复杂度是 O(nn+n×K105+nKn) ,设 n=105 ,那么当 K=4n 时复杂度达到最优,为 O(n47) 。
然而这是 YNOI , lxl 是不会让你这么简单就通过的,你会获得 100×((25)2+(45)2)221 的好成绩。
注意到上述阈值 K 的设定是十分宽泛的,只需要将上述阈值 K 改为 3 ,就可以真正的获得 ((25)2+(45)2)2 的坏成绩,连 lxl 也无法阻止你。
见代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| #include <bits/stdc++.h> using namespace std; const int N=100010,B=350; int n,m,a[N],b[N],ans[N],hav[N]; bitset<N>ex,rev; struct node{ int l,r,id,x,opt; }q[N]; vector<int>v; bool operator<(node x,node y){ return x.l/B==y.l/B?x.r/B<y.r/B:x.l/B<y.l/B; } void add(int x){ b[x]++;ex[x]=1;rev[N-x]=1; } void del(int x){ b[x]--;if(b[x]==0)ex[x]=0,rev[N-x]=0; } void addd(int x){ b[x]++; if(b[x]>1)return ; for(int i=1;i<=3;i++){ if(x%i==0&&b[x/i]){ hav[i]++; } } for(int i=2;i<=3;i++){ if(x*i<=1e5&&b[x*i]){ hav[i]++; } } } void dell(int x){ b[x]--; if(!b[x]){ hav[1]--; for(int i=1;i<=3;i++){ if(x%i==0&&b[x/i]){ hav[i]--; } } for(int i=1;i<=3;i++){ if(x*i<=1e5&&b[x*i]){ hav[i]--; } }} } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%d",a+i); for(int i=1;i<=m;i++){ int opt,l,r,x; scanf("%d%d%d%d",&opt,&l,&r,&x); q[i]={l,r,i,x,opt}; } sort(q+1,q+1+m); int l=1,r=0,k=0; for(int i=1;i<=m;i++){ if(q[i].opt==4&&q[i].x<=3){ q[++k]=q[i]; continue; } while(l>q[i].l)l--,add(a[l]); while(r<q[i].r)r++,add(a[r]); while(l<q[i].l)del(a[l]),l++; while(r>q[i].r)del(a[r]),r--; if(q[i].opt==1){ if((ex&(ex<<q[i].x)).any())ans[q[i].id]=1; } if(q[i].opt==2){ if((ex&(rev>>N-q[i].x)).any())ans[q[i].id]=1; } if(q[i].opt==3){ for(int j=1;j*j<=q[i].x;j++){ if(q[i].x%j==0&&ex[j]&&ex[q[i].x/j]){ ans[q[i].id]=1; break; } } } if(q[i].opt==4){ if(q[i].x==1){ans[q[i].id]=1;continue;} if(q[i].x==0)continue; for(int j=1;j*q[i].x<=1e5;j++){ if(ex[j]&&ex[j*q[i].x]){ ans[q[i].id]=1; break; } } } } memset(b,0,sizeof(b)); l=1;r=0; sort(q+1,q+1+k); for(int i=1;i<=k;i++){ while(l>q[i].l)l--,addd(a[l]); while(r<q[i].r)r++,addd(a[r]); while(l<q[i].l)dell(a[l]),l++; while(r>q[i].r)dell(a[r]),r--; ans[q[i].id]=hav[q[i].x]; } for(int i=1;i<=m;i++){ if(ans[i])printf("yuno\n"); else printf("yumi\n"); } return 0; }
|