2019.10.21 NOIP模拟测试 day1
AFO倒计时...感觉10月28日就完了。
3道题都是洛谷某次月赛的题目。
题解:
这道题一开始我还不太懂什么意思,就觉得很懵逼,推了很久也没有发现什么规律。差点就建了一棵树跳lca。其实完全没有必要,还是好想,我的思路就是记录每一个节点是哪一代出生的,然后你会发现每个节点的儿子与他的父亲相差都为一个斐波拉契数列中的数,于是我们可以这样想,就是按照找lca的思路来跳,然后每次出生靠后的节点往上跳,每次跳的值就是斐波拉契数列中的它出生那一代的项数。数据范围是10^12,所以要处理到菲波拉切数列的60项即可。避免了建树,还是比较好想的。
代码如下:
#include<bits/stdc++.h> using namespace std; const int maxn=1e6+7; long long f[maxn]; int m; long long a,b; int tot; int ma,mb; bool flag; inline long long read(){ long long rt = 0 ; char ch = getchar() ; while( ch < '0' || ch > '9' ) ch = getchar() ; while( ch >='0' && ch <='9' ) rt = (rt<<1)+(rt<<3) + ch - '0' , ch = getchar() ; return rt ; } long long lca(long long x,long long y){ while(x!=y){ if(ma>mb){//深度大的往上跳 x-=f[ma]; if(x==1) ma=1; else{ for(int i=2;i<=60;i++){ if(x>f[i-1]&&x<=f[i]) ma=i-1; } } } else{ y-=f[mb]; if(y==1) mb=1; else{ for(int i=2;i<=60;i++){ if(y>f[i-1]&&y<=f[i]) mb=i-1; } } } } return x; } int main(){ scanf("%d",&m); f[1]=1;f[2]=1; for(int i=3;i<=60;i++) f[i]=f[i-1]+f[i-2]; for(int i=1;i<=m;i++){ a=read();b=read(); tot=0;flag=false;ma=mb=0; if(a==b){ printf("%lld\n",b); continue; } else{ for(int j=2;j<=60;j++){ if(a>f[j-1]&&a<=f[j]){ ma=j-1; break; } } for(int j=2;j<=60;j++){ if(b>f[j-1]&&b<=f[j]){ mb=j-1; break; } } printf("%lld\n",lca(a,b)); } } return 0; }
题解:
暴力做法是个人都会写,考试的时候只打了暴力做法,原本以为只能得30分,结果出题人造的数据比较良心,还是水到了60分。其实正解二分答案我觉得很难想到,所以我还是选择往数据结构的方面来想。
动态开点线段树即可,每一棵线段树维护每一种颜色,颜色一共有300000种,动态开点足矣。然后交换的话就是简单的单点修改。坑点:注意每次单点修改时要将原序列中的颜色交换,还需要小小的卡常。
代码如下:
#include<bits/stdc++.h> using namespace std; const int maxn=300005; int lc[maxn*30],rc[maxn*30],root[maxn]; int n,m; int a[maxn]; int sum[maxn*30]; int cnt; int opt,l,r,x,c; inline int read(){ int a=0;char x=getchar(); while(x<'0'||x>'9')x=getchar(); while(x>='0'&&x<='9')a=(a<<3)+(a<<1)+x-48,x=getchar(); return a; } void update(int &rt,int l,int r,int pos,int v){ if(!rt) rt=++cnt; sum[rt]+=v; if(l==r) return; int mid=(l+r)>>1; if(pos<=mid) update(lc[rt],l,mid,pos,v); else update(rc[rt],mid+1,r,pos,v); } int query(int rt,int l,int r,int l1,int r1){ if(!rt) return 0; if(l>=l1&&r1>=r) return sum[rt]; int mid=(l+r)>>1; int ans=0; if(l1<=mid) ans+=query(lc[rt],l,mid,l1,r1); if(r1>mid) ans+=query(rc[rt],mid+1,r,l1,r1); return ans; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ a[i]=read(); update(root[a[i]],1,n,i,1); } for(int i=1;i<=m;i++){ scanf("%d",&opt); if(opt==1){ l=read();r=read();c=read(); printf("%d\n",query(root[c],1,n,l,r)); } else{ x=read(); update(root[a[x]],1,n,x,-1); update(root[a[x+1]],1,n,x+1,-1); update(root[a[x]],1,n,x+1,1); update(root[a[x+1]],1,n,x,1); swap(a[x],a[x+1]); } } return 0; }
题解:
这道题的题面很长,对于这种题面很长的题,读题时就需要耐心,你发现你读完了题面就能得到8分。这道题数据范围写的非常明确,还是很好得分的。对于k=1的情况,我们只需要让相加为平方数的数不在一个序列中即可。k=2时,需要用并查集来辅助,具体过程见代码,又是并查集的神奇运用。
#include<bits/stdc++.h> using namespace std; const int maxn=1e6+7; bool power[maxn]; int n,k; long long sum; int a[maxn]; bool flag; int ans[maxn]; int cnt; int last; int maxx; int vis[maxn]; int fa[maxn]; int biaoji[maxn]; int get(int x){ if(x==fa[x]) return x; return fa[x]=get(fa[x]); } void work1(){ for(int i=n;i>=1;i--){ flag=false; for(int j=1;j<=512;j++){ if(j*j<=a[i]) continue; if(j*j-a[i]>maxx) break; if(vis[j*j-a[i]]){ flag=true; break; } } if(flag){ ans[++cnt]=i; for(int j=i+1;j<=last;j++) vis[a[j]]=0; last=i; } vis[a[i]]=1; } } void work2(){ for(int i=1;i<=maxx*2;i++) fa[i]=i; for(int i=1;i*i<=maxx*2;i++) biaoji[i*i]=1; for(int i=n;i>=1;i--){ flag=0; if(vis[a[i]]){ if(biaoji[a[i]*2]){ for(int j=1;j<=512;j++){ if(vis[a[i]]==2||fa[a[i]+maxx]!=a[i]+maxx){ flag=true; break; } if(j*j<a[i]) continue; if(j*j-a[i]>maxx) break; if(vis[j*j-a[i]]&&j*j-a[i]!=a[i]){ flag=true; break; } } if(flag){ ans[++cnt]=i; for(int j=i;j<=last;j++){ fa[a[j]]=a[j]; fa[a[j]+maxx]=a[j]+maxx; vis[a[j]]=0; } last=i; } } } else{ for(int j=1;j<=512;j++){ if(j*j<a[i]) continue; if(j*j-a[i]>maxx) break; if(vis[j*j-a[i]]){ if(biaoji[2*(j*j-a[i])]&&vis[j*j-a[i]]==2){ flag=true; break; } int f1=get(a[i]),f2=get(a[i]+maxx),f3=get(j*j-a[i]),f4=get(j*j-a[i]+maxx); if(f1==f3){ flag=true; break; } else{ fa[f4]=f1; fa[f2]=f3; } } } if(flag){ ans[++cnt]=i; for(int j=i;j<=last;j++){ fa[a[j]]=a[j],fa[a[j]+maxx]=a[j]+maxx; vis[a[j]]=0; } last=i; } } vis[a[i]]++; } } int main(){ //freopen("division.in","r",stdin); //freopen("division.out","w",stdout); scanf("%d%d",&n,&k); last=n; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); maxx=max(a[i],maxx); } if(k==1) work1(); else work2(); printf("%d\n",cnt+1); for(int i=cnt;i>=1;i--) printf("%d ",ans[i]); printf("\n"); return 0; }