【浮*光】#省选真题# [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);
}
【p3245】大数 //莫队

 

这题其实还比较温和,不过又是个记录后缀的...应该能得一点分...?

 


 

 

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]);

}
【p3246】序列 // 莫队

 

如果是考场上的话,这题绝对做不出来...就看有没有暴力分了...

(这种神奇的维护前缀后缀的方法我肯定想不到&想到了也会写挂啊...)

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;
}
【p3247】最小公倍数 // 并查集 + 分块

 

好吧,这种题我也是是实在不会做...并查集+分块维护路径?真的搞不懂...期望得分 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]));
    }
}
【p3250】网络 // 树链剖分

 


 

 

 

                                                   ——时间划过风的轨迹,那个少年,还在等你

 

posted @ 2019-03-21 17:01  花神&缘浅flora  阅读(164)  评论(0编辑  收藏  举报