【浮*光】#省选真题# [HNOI2016]
自从被 LRZ dalao 通知,HB在2016年之前都是做的HNOI,我就...
有一种难以言喻的神奇感觉?然后莫名害怕???
题目真心做的超慢啊(说的像我真的会做一样...)
T1:【p3245】大数
- 一个数字串,m个询问,每次询问串[l,r]中‘%P=0的子串个数’ 。
存数字串的后缀:rs[i]表示i到n形成的数%p的值。
那么数字[l,r]=num(l,r)≡ rs[l]−rs[r+1] / 10^(r−l+1) (mod p)。
当10^(r−l+1)%p≠0时:num(l,r)∗10^(r−l+1)≡rs[l]−rs[r+1](mod p)。
所以如果rs[l]=rs[r+1],num(l,r)就是p的倍数,条件是gcd(10,p)=1,p不能是2或5。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; // 【p3245】大数 // 一个数字串,m个询问,每次询问串[l,r]中‘%P=0的子串个数’ 。 /*【莫队】存数字串的后缀:rs[i]表示i到n形成的数%p的值。 那么数字[l,r]=num(l,r)≡ rs[l]−rs[r+1] / 10^(r−l+1) (mod p)。 当10^(r−l+1)%p≠0时:num(l,r)∗10^(r−l+1)≡rs[l]−rs[r+1](mod p)。 所以如果rs[l]=rs[r+1],num(l,r)就是p的倍数,条件是gcd(10,p)=1,p不能是2或5。*/ void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fx=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fx; //正负号 } const int N=500019; int p,n,m,pos[N],a[N],rs[N],mi[N]; ll cnt[N],sum=0,cntt=0,l,r; ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);} struct number{ int z,id; }b[N]; ll sqr(ll x){return x*x;} struct node{int l,r,id;ll ans;}q[N]; bool cmp(node a,node b) //【分块优化排序】同块中按r排序,不同块时按l排序 { if(pos[a.l]==pos[b.l]) return a.r<b.r; return a.l<b.l; } bool cmp2(number a,number b){return a.z<b.z;} bool cmp_id(node a,node b){return a.id<b.id;} inline void addl(int x){cntt+=(a[x]%p==0),sum+=cntt;} inline void addr(int x){sum+=(r-l+1)*(a[x]%p==0),cntt+=(a[x]%p==0);} inline void dell(int x){sum-=cntt,cntt-=(a[x]%p==0);} inline void delr(int x){sum-=(r-l+1)*(a[x]%p==0),cntt-=(a[x]%p==0);} inline void add(int x){sum+=cnt[rs[x]],cnt[rs[x]]++;} inline void del(int x){cnt[rs[x]]--,sum-=cnt[rs[x]];} void get_25(){ l=1; for(int i=1;i<=m;i++){ while(l<q[i].l) dell(l),l++; while(l>q[i].l) l--,addl(l); while(r>q[i].r) delr(r),r--; while(r<q[i].r) r++,addr(r); q[i].ans=sum; } } void solve(){ l=1; for(int i=1;i<=m;i++){ while(l<q[i].l) del(l),l++; while(l>q[i].l) l--,add(l); while(r>q[i].r+1) del(r),r--; while(r<q[i].r+1) r++,add(r);q[i].ans=sum; } } char ss[100019]; int main(){ reads(p),scanf("%s",ss+1); n=strlen(ss+1); int block=(int)sqrt(n); for(int i=1;i<=n;i++) a[i]=ss[i]-'0',pos[i]=(i-1)/block+1; mi[1]=1; for(int i=2;i<=n;i++) mi[i]=mi[i-1]*10%p; if(p!=2&&p!=5){ for(int i=n;i>=1;i--){ b[i].z=(b[i+1].z+a[i]*mi[n-i+1]%p)%p,b[i].id=i; } b[n+1].id=n+1; b[n+1].z=0; //z记录后缀%p得到的值(未离散化) sort(b+1,b+n+2,cmp2); //排序,这样就可以将rs数组离散化 for(int i=1;i<=n+1;i++) rs[b[i].id]=rs[b[i-1].id]+(b[i].z!=b[i-1].z); } //↑↑计算后缀数%p之后得到的值(rs[]),离散化rs数组 reads(m); for(int i=1;i<=m;i++) reads(q[i].l),reads(q[i].r),q[i].id=i; sort(q+1,q+m+1,cmp); if(p!=2&&p!=5) solve(); //离线,进行莫队算法 else get_25(); //特殊情况:重开一个莫队处理2和5的情况 sort(q+1,q+m+1,cmp_id); for(int i=1;i<=m;i++) printf("%lld\n",q[i].ans); }
这题其实还比较温和,不过又是个记录后缀的...应该能得一点分...?
T2:【p3246】序列
- q次询问,每次询问区间[l,r]的‘所有子区间的最小值’的sum值。
预处理出所有的‘l,r’答案,再O(1)输出?←你怕不是疯了...
一看就是莫队啊,就是中间一群东西不会处理...
然后做(chao)这(dai)题(ma)的时候都困得要睡着了...
十分之无奈...我TM怎么办啊...各种心累...
f[i]表示以i结尾的每个‘后缀’的区间最小值之和。
预处理出pre[i]:i前面的第一个小于它的元素位置。
转移: f[i]=f[pre[i]]+(i−pre[i])×a[i] 。
那么怎么求区间[L,R],[L+1,R]....[R,R]的贡献?
找[L,R]区间内的最小值位置po,在后方新加入一个数,那么:
- 在[L,po]区间内的答案为a[po]。
- 在[po+1,R]之间的答案为f[R]−f[po]。
同理,记录: nxt[] 表示 从后面数,第一个小于它的元素的位置,
g[i] 表示 以i结尾的每个‘前缀’的区间最小值之和。
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <queue> #include <stack> #include <map> using namespace std; typedef long long ll; typedef unsigned int uint; typedef unsigned long long ull; /*【p3246】序列 // 莫队 q次询问,每次询问区间[l,r]的‘所有子区间的最小值’的sum值。*/ //预处理出所有的‘l,r’答案,再O(1)输出?←你怕不是疯了... //一看就是莫队啊,就是中间一群东西不会处理... /* f[i]表示以i结尾的每个‘后缀’的区间最小值之和。预处理出pre[i]:i前面的第一个小于它的元素位置。 转移: f[i]=f[pre[i]]+(i−pre[i])×a[i]; 那么怎么求区间[L,R],[L+1,R]....[R,R]的贡献? 找[L,R]区间内的最小值位置po,左端点在[L,po]区间内的答案为a[po];在[po+1,R]之间的答案为f[R]−f[po]。 那么nxt[]:从后面数,第一个小于它的元素的位置;g[i]表示以i结尾的每个‘前缀’的区间最小值之和。*/ void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; /* hs_love_wjy */ } const int N=1000019,inf=(int)2e9; int n,ques,a[N],q[N],pre[N],nxt[N],pos[N][22]; ll f[N],g[N],ans[N]; int lg[N]; //倍增数组 int get(int x,int y){ if(a[x]<a[y]) return x; return y; } struct ques_{ int l,r,id,bl; }b[N]; //每个询问 bool cmp(ques_ a,ques_ b) //莫队排序:分块 { if(a.bl==b.bl) return a.r<b.r; return a.bl<b.bl; } ll add(int l,int r){ int len=r-l+1; //区间内,倍增法找出最小值位置 int po=get(pos[l][lg[len]],pos[r-(1<<(lg[len]))+1][lg[len]]); ll tmp=0; tmp=(ll)(po-l+1)*a[po]; tmp+=f[r]-f[po]; return tmp; } ll del(int l,int r){ int len=r-l+1; //区间内,倍增法找出最小值位置 int po=get(pos[l][lg[len]],pos[r-(1<<(lg[len]))+1][lg[len]]); ll tmp=0; tmp=(ll)(r-po+1)*a[po]; tmp+=g[l]-g[po]; return tmp; } int main(){ reads(n),reads(ques); for(int i=1;i<=n;i++) reads(a[i]); int l,r; a[0]=a[n+1]=-inf; //初始点初始化 l=1,r=0; q[++r]=0; for(int i=1;i<=n;i++) { while(l<=r&&a[q[r]]>=a[i]) r--; pre[i]=q[r],q[++r]=i; } l=1,r=0; q[++r]=n+1; for(int i=n;i>=1;i--) { while(l<=r&&a[q[r]]>=a[i]) r--; nxt[i]=q[r],q[++r]=i; } for(int i=1;i<=n;i++) f[i]=f[pre[i]]+(ll)(i-pre[i])*a[i]; for(int i=n;i>=1;i--) g[i]=g[nxt[i]]+(ll)(nxt[i]-i)*a[i]; for(int i=1;i<=n;i++) pos[i][0]=i,lg[i]=(int)(log(i)/log(2)); for(int j=1;j<=20;j++) for(int i=1;i<=n;i++) //倍增 pos[i][j]=get(pos[i][j-1],pos[i+(1<<(j-1))][j-1]); int block=sqrt(n); for(int i=1;i<=ques;i++) reads(b[i].l),reads(b[i].r),b[i].id=i,b[i].bl=(b[i].l-1)/block+1; sort(b+1,b+ques+1,cmp); int L=1,R=0; ll now=0; for(int i=1;i<=ques;i++){ //莫队部分 while(R<b[i].r){ R++; now+=add(L,R); } while(R>b[i].r){ now-=add(L,R); R--; } while(L<b[i].l){ now-=del(L,R); L++; } while(L>b[i].l){ L--; now+=del(L,R); } ans[b[i].id]=now; //记录每次询问的答案 } for(int i=1;i<=ques;i++)printf("%lld\n",ans[i]); }
如果是考场上的话,这题绝对做不出来...就看有没有暴力分了...
(这种神奇的维护前缀后缀的方法我肯定想不到&想到了也会写挂啊...)
p.s. 话说这是 莫队+ 前后缀处理 专场吗???什么鬼神奇玩意儿啊...
T3:【p3247】最小公倍数
- N个顶点M条边的无向图,有边权。边权都可以分解成2^a*3^b的形式。
- 现在有q个询问,每次询问给定四个参数u、v、a和b。输出Yes/No。
- 求出是否存在u到v的路径,使得依次经过的边权的最小公倍数为2^a*3^b。
一眼看上去就是个毒瘤题...怎么处理各种路径啊...边权也不好维护啊...
思路:只记录每条边的(a,b),那么询问就变为:路径上max_a=a,max_b=b。
【分析】将边按a从小到大排序,每√m个取一个关键点。
对于每个关键点,将这个点之前的边以及要在这个关键点回答的询问按b排序。
依次加入这个关键点之前的每条边,用并查集维护每个连通块a和b的最大值。
对于零碎部分,只有√m条边,暴力加入。用栈按时间记录所有修改操作。
#include <cmath> #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <algorithm> #include <queue> #include <stack> #include <map> using namespace std; typedef long long ll; typedef unsigned int uint; typedef unsigned long long ull; /*【p3247】最小公倍数 // 并查集 + 分块(?) N个顶点M条边的无向图,有边权。边权都可以分解成2^a*3^b的形式。 现在有q个询问,每次询问给定四个参数u、v、a和b。输出Yes/No。 求出是否存在u到v的路径,使得依次经过的边权的最小公倍数为2^a*3^b。*/ //一眼看上去就是个毒瘤题...怎么处理各种路径啊...边权也不好维护啊... //思路:只记录每条边的(a,b),那么询问就变为:路径上max_a=a,max_b=b。 /*【分析】将边按a从小到大排序,每√m个取一个关键点。 对于每个关键点,将这个点之前的边以及要在这个关键点回答的询问按b排序。 依次加入这个关键点之前的每条边,用并查集维护每个连通块a和b的最大值。 对于零碎部分,只有√m条边,暴力加入。用栈按时间记录所有修改操作。 */ // ↑↑ dalao说的看懂了吗?当然没有... void reads(int &x){ //读入优化(正负整数) int fx=1;x=0;char ch_=getchar(); while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();} while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();} x*=fx; /* hs_love_wjy */ } const int N=50010,M=100010; int n,m,Q,lim,i,j,k,o,x,y,ans[N],cnt,a[M],b[N]; int f[N],d[N],va[N],vb[N],co; struct P{int x,y,z;P(){}P(int _x,int _y,int _z){x=_x,y=_y;z=_z;}}op[N]; struct E{int x,y,a,b;}e[M],q[N]; inline bool cmpa(const E&a,const E&b){return a.a<b.a;} inline bool cmpe(int x,int y){return e[x].b<e[y].b;} inline bool cmpq(int x,int y){return q[x].b<q[y].b;} int find_fa(int x){return f[x]==x?x:find_fa(f[x]);} inline void merge(int x,int y,int a,int b,int type){ x=find_fa(x),y=find_fa(y); if(x!=y){ if(d[x]==d[y]) { if(type) op[++co]=P(1,x,d[x]); d[x]++; } if(d[x]<d[y]) swap(x,y); if(type) op[++co]=P(0,y,y); f[y]=x; if(va[x]<va[y]&&va[y]>a) { if(type) op[++co]=P(2,x,va[x]); va[x]=va[y]; } if(vb[x]<vb[y]&&vb[y]>b) { if(type) op[++co]=P(3,x,vb[x]); vb[x]=vb[y]; } } if(va[x]<a){ if(type) op[++co]=P(2,x,va[x]); va[x]=a; } if(vb[x]<b){ if(type) op[++co]=P(3,x,vb[x]); vb[x]=b; } } inline void retrace(){ for(int i=co;i;i--){ if(!op[i].x) f[op[i].y]=op[i].z; else if(op[i].x==1) d[op[i].y]=op[i].z; else if(op[i].x==2) va[op[i].y]=op[i].z; else vb[op[i].y]=op[i].z; } co=0; } int main(){ reads(n),reads(m); while(lim*lim<m) lim++; for(i=1;i<=m;i++) reads(e[i].x),reads(e[i].y), reads(e[i].a),reads(e[i].b),a[i]=i; sort(e+1,e+m+1,cmpa); reads(Q); for(i=1;i<=Q;i++) reads(q[i].x),reads(q[i].y),reads(q[i].a),reads(q[i].b); for(i=0;i<=m;i+=lim){ cnt=0; for(j=1;j<=Q;j++) if(q[j].a>=e[i].a&&(i+lim>m||q[j].a<e[i+lim].a)) b[++cnt]=j; if(!cnt) continue; sort(a+1,a+i+1,cmpe),sort(b+1,b+cnt+1,cmpq); for(j=1;j<=n;j++) f[j]=j,d[j]=0,va[j]=vb[j]=-1; for(j=k=1;j<=cnt;j++){ while(k<=i&&e[a[k]].b<=q[b[j]].b) merge(e[a[k]].x,e[a[k]].y,e[a[k]].a,e[a[k]].b,0),k++; co=0; for(o=min(i+lim-1,m);o>i;o--) if(e[o].a<=q[b[j]].a&&e[o].b<=q[b[j]].b) merge(e[o].x,e[o].y,e[o].a,e[o].b,1); x=find_fa(q[b[j]].x),y=find_fa(q[b[j]].y); if(x==y&&va[x]==q[b[j]].a&&vb[x]==q[b[j]].b) ans[b[j]]=1; retrace(); } } for(i=1;i<=Q;i++) puts(ans[i]?"Yes":"No"); return 0; }
好吧,这种题我也是是实在不会做...并查集+分块维护路径?真的搞不懂...期望得分 0。我不再说话了。
T4:【p3248】树
T5:【p3249】矿区
T6:【p3250】网络
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; /*【p3250】网络*/ // https://www.cnblogs.com/Y-E-T-I/p/7048360.html void reads(int &x){ //读入优化(正负整数) int fa=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();} while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();} x*=fa; //正负号 } const int N=200019; int n,m,dat1[N],dat2[N],dat3[N]; struct segment_tree{ priority_queue<int>d1,d2; //每个线段树节点对应两个大根堆 void push(int x){d1.push(x);} void del(int x){d2.push(x);} int top(){ // 设置两个堆,便于删除操作 ↑↑ while(!d2.empty()&&d1.top()==d2.top()) d1.pop(),d2.pop(); if(d1.empty()) return -1; return d1.top(); } }c[N*5]; struct Data{ int l,r; }data[N]; bool cmp(Data a,Data b){ return a.l<b.l; } int head[N],tot,son[N],siz[N],fa[N],dep[N],top[N],id[N],cnt; struct node{ int nextt,ver,w; }e[N]; void add(int x,int y) { e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; } //--------线段树部分----------// void update(int rt,int l,int r,int L,int R,int type,int val){ if(L>R) return; if(l>=L&&r<=R){ if(type) c[rt].del(val); else c[rt].push(val); return; } int mid=(l+r)/2; if(L<=mid) update(rt<<1,l,mid,L,R,type,val); if(R>mid) update(rt<<1|1,mid+1,r,L,R,type,val); } void change(int x,int y,int type,int val){ int sum=0; while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x,y); data[++sum].l=id[top[x]]; data[sum].r=id[x],x=fa[top[x]]; } if(dep[x]<dep[y]) swap(x,y); if(x!=y) data[++sum].l=id[y],data[sum].r=id[x]; sort(data+1,data+sum+1,cmp); int las_=1; for(int i=1;i<=sum;i++) update(1,1,n,las_,data[i].l-1,type,val), las_=data[i].r+1; update(1,1,n,las_,n,type,val); } int query(int rt,int l,int r,int x){ if(l==r) return c[rt].top(); int mid=(l+r)/2; if(x<=mid) return max(c[rt].top(),query(rt<<1,l,mid,x)); else return max(c[rt].top(),query(rt<<1|1,mid+1,r,x)); } //--------树链剖分部分----------// void dfs1(int u,int fa_){ //第一遍dfs:求子树大小和重儿子 siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1; for(int i=head[u];i;i=e[i].nextt){ if(e[i].ver==fa_) continue; dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子 } } void dfs2(int u,int fa_){ //第二遍dfs:确定dfs序和top值 if(son[u]){ //先走重儿子,使重链在线段树中的位置连续 id[son[u]]=++cnt; //节点记入线段树中 top[son[u]]=top[u],dfs2(son[u],u); //更新top值 } for(int i=head[u];i;i=e[i].nextt){ if(top[e[i].ver]) continue; //除去u的重儿子或父亲 id[e[i].ver]=++cnt; //加入线段树 top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u); //轻边上的点 } } //--------主程序部分----------// int main(){ int u,v,w,x,op; reads(n),reads(m); for(int i=1;i<n;i++) reads(u),reads(v),add(u,v),add(v,u); dfs1(1,0),cnt=id[1]=top[1]=1,dfs2(1,0); for(int i=1;i<=m;i++){ reads(op); if(op==0) reads(u),reads(v),reads(w), dat1[i]=u,dat2[i]=v,dat3[i]=w,change(u,v,0,w); if(op==1) reads(x),u=dat1[x],v=dat2[x],w=dat3[x],change(u,v,1,w); if(op==2) reads(x),printf("%d\n",query(1,1,n,id[x])); } }
——时间划过风的轨迹,那个少年,还在等你