NOIP2017赛前模拟11月4日总结:
第一次挂0·····有点感伤···主要是因为时间分配太不合理了··花2个半小时搞第一题最后还wa完了··第二题很简单花了30分钟打完但没打对拍结果wa完···第三题暴力可以拿20分的但没时间打了···
第一次感受到了暴力的重要性··第一是想不出正解部分分是要拿的··第二是即使想出正解对拍也要用暴力···
以后考试决定遇到一道题先只想个20分钟·如果想不出正解先把暴力打了··三道题这样弄完后再去细细想正解
题目1:区间
给定一个n个正整数的序列··q次询问两个数a,b,问序列中有多少个区间使得ab出现次数相等(0次也算),n<=8000,q<=500000
先说暴力的方法··我们统计完前缀和后找出满足题意的区间lr为sum[a][r]-sum[a][l-1]=sum[b][r]-sum[b][l-1],移项后为sum[a][r]-sum[b][r]=sum[a][l-1]-sum[b][l-1],因此对于每次询问直接开一个桶记录每个两个数每一个位置的sum[a][i]-sum[b][i]的值的总数计算即可···顺便讨论一下ab是否存在于序列中的情况,注意ab有可能相等
其实正解只是暴力的优化····这道题明显也想不出什么巧妙的方法··第一点是我们可以发现我们计算sum[a][i]-sum[b][i]的效果等效于我们在遍历每个位置时记录一个tag,遇到a的话+1,遇到b的话-1,每个位置上的tag值实际上就是sum[a][i]-sum[b][i],因此不用统计前缀和直接扫就可以了··第二点是我们可以发现暴力是对于每次询问我们是暴力O(n)扫过去的··其实可以发现中途的不是ab的位置实际上是多余的···因此我们完全可以开个数组记录每个数出现的位置(推荐用vector动态开··很方便··考试时脑子抽了写了个手动数组麻烦死··)对于询问的两个数将两个数的位置数组按序合并然后直接在位置数组上扫就可以了·····可以证明优化后的复杂度是n^2的
代码:
#include<iostream> #include<cstdio> #include<ctime> #include<cctype> #include<string> #include<cstring> #include<cmath> #include<string> #include<algorithm> #include<vector> using namespace std; const int N=50005; const int M=5e5+5; inline int R(){ char c;int f=0; for(c=getchar();c<'0'||c>'9';c=getchar()); for(;c<='9'&&c>='0';c=getchar()) f=(f<<3)+(f<<1)+c-'0'; return f; } struct node{int x,y;}q[M]; vector<int>pos[N]; int tub[N*2],n,Q,b[N],cnt,num[N],tim,visit[N*2]; inline void lsh(){ sort(b+1,b+cnt+1); cnt=unique(b+1,b+cnt+1)-b-1; for(int i=1;i<=n;i++) num[i]=lower_bound(b+1,b+cnt+1,num[i])-b; } int main(){ n=R(),Q=R(); for(int i=1;i<=n;i++) num[i]=R(),b[++cnt]=num[i]; lsh(); for(int i=1;i<=Q;i++){ q[i].x=R(),q[i].y=R(); int tx=lower_bound(b+1,b+cnt+1,q[i].x)-b; int ty=lower_bound(b+1,b+cnt+1,q[i].y)-b; if(b[tx]!=q[i].x) q[i].x=0; else q[i].x=tx; if(b[ty]!=q[i].y) q[i].y=0; else q[i].y=ty; } for(int i=1;i<=n;i++) pos[num[i]].push_back(i); for(register int i=1;i<=Q;i++){ if((!q[i].x&&!q[i].y)||q[i].x==q[i].y){cout<<(n+1)*n/2<<endl;continue;} static int loc[N];int temp=0,a=q[i].x,b=q[i].y; register int head1=0,head2=0; while(head1<pos[a].size()||head2<pos[b].size()){ if(head1==pos[a].size()){ while(head2<pos[b].size()) loc[++temp]=pos[b][head2],head2++; continue; } if(head2==pos[b].size()){ while(head1<pos[a].size()) loc[++temp]=pos[a][head1],head1++; continue; } if(pos[a][head1]>pos[b][head2]) loc[++temp]=pos[b][head2],head2++; else loc[++temp]=pos[a][head1],head1++; } if(!a||!b){ long long ans=0; for(int i=1;i<=temp;i++) ans+=(loc[i]-loc[i-1]-1)*(loc[i]-loc[i-1])/2; ans+=(n-loc[temp])*(n-loc[temp]+1)/2; cout<<ans<<endl; } else{ tim++;int maxx,minn,tag=N; tub[N]=1;visit[N]=tim;maxx=minn=N;long long ans=0; for(register int i=1;i<=temp;i++){ if(num[loc[i]]==a){ tag++;maxx=max(maxx,tag),minn=min(minn,tag); if(visit[tag]!=tim) tub[tag]=1,visit[tag]=tim; else tub[tag]++; } else{ tag--;maxx=max(maxx,tag),minn=min(minn,tag); if(visit[tag]!=tim) tub[tag]=1,visit[tag]=tim; else tub[tag]++; } if(i!=temp) tub[tag]+=loc[i+1]-loc[i]-1; else tub[tag]+=n-loc[i]; } tub[N]+=loc[1]-1; for(register int i=minn;i<=maxx;i++) if(visit[i]==tim) ans+=tub[i]*(tub[i]-1)/2; cout<<ans<<endl; } } return 0; }
题目2:排列
给定n个数的排列的每个位置上的数的逆序数的前缀和(逆序数:前面的数中比该数大的数的个数),求出该排列····
很简单的一道题··我们先将通过前缀和直接算出每个数的逆序数(从后往前一次用这个位置的前缀和减去下一个位置的前缀和考试的时候我是从前往后减的坑爹样例没有看出错),这时我们从后往前以此处理···很明显对于每个位置的逆序数a,我们知道它前面的数中有多少个数比它大从而知道了它的排名··直接用线段树区间查询k大值就可以了,再将它删除
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<ctime> #include<cctype> #include<string> #include<cstring> #include<algorithm> using namespace std; inline long long R(){ char c;long long f=0; for(c=getchar();c<'0'||c>'9';c=getchar()); for(;c<='9'&&c>='0';c=getchar()) f=f*10+c-'0'; return f; } const int N=1e5+5; int tree[N*4],n,anss[N],temp; long long num[N]; inline void build(int k,int l,int r){ if(l==r){ tree[k]++;return; } int mid=(l+r)/2; build(k*2,l,mid);build(k*2+1,mid+1,r); tree[k]=tree[k*2]+tree[k*2+1]; } inline void find(int k,int l,int r,int x){ if(l==r){ tree[k]--;temp=l;return; } int mid=(l+r)/2; if(x<=tree[k*2]) find(k*2,l,mid,x); else find(k*2+1,mid+1,r,x-tree[k*2]); tree[k]=tree[k*2]+tree[k*2+1]; } int main(){ //freopen("premu.in","r",stdin); //freopen("premu.out","w",stdout); n=R(); for(int i=1;i<=n;i++) num[i]=R(); for(int i=n;i>=1;i--) num[i]-=num[i-1]; build(1,1,n); for(int i=n;i>=1;i--){ find(1,1,n,i-num[i]); anss[i]=temp; } for(int i=1;i<=n;i++) cout<<anss[i]<<" "; return 0; }
题目3:边的处理
有一个n个点的无向图,给出m条边,每条边的信息形如x,y,c,r
给出q组询问形如u,v,l,r
接下来解释询问以及边的意义。
询问表示,一开始你在点u上,然后按顺序处理编号从l到r的边。
对于一条边xycr,你可以进行两次操作:
1、如果你当前在x点或者y点上,那么你可以走这条边(从x到y或从y到x)并付出c的代价(当然你也可以不走,看操作2)。
2、如果你不走这条边或者不可以走这条边(即你当前不在x或y上),那么你需要付出r的代价。
询问如果要从点u开始,按顺序处理完编号从l到r的边之后到达点v的最小代价,如果不能到达u,那么输出-1
n<=30,m<=20000,q<=200000
很好的分治题···
暴力的话我们考虑对于每次询问的话每次处理到某一条边时所在点的所有的情况然后想最短路一样更新它到起点的距离
分治的话挺复杂的,具体见http://blog.csdn.net/qq_35649707/article/details/78439108,%%%%%%%%%
代码:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<ctime> #include<cstring> #include<string> #include<algorithm> #include<cstring> using namespace std; const int M=2e4+5; const int N=2e5+5; const int inf=0x3f3f3f3f; inline int R(){ char c;int f=0; for(c=getchar();c<'0'||c>'9';c=getchar()); for(;c<='9'&&c>='0';c=getchar()) f=(f<<3)+(f<<1)+c-'0'; return f; } struct node{ int x,y,c,r,u,v,l,id; }ed[M],q[N]; int n,m,Q,f[M][35][35],anss[N]; inline void solve(int l,int r,int x,int y){ if(x>y) return; if(l==r){ if(ed[l].x>ed[l].y) swap(ed[l].x,ed[l].y); for(int i=x;i<=y;i++){ if(q[i].u>q[i].v) swap(q[i].u,q[i].v); if(q[i].l==l&&q[i].u==ed[l].x&&q[i].v==ed[l].y) anss[q[i].id]=ed[l].c; if(q[i].u==q[i].v) anss[q[i].id]=min(anss[q[i].id],ed[l].r); } return; } static node d[N];int ri=y+1,le=x-1,mid=(l+r)/2; memset(f[mid],inf,sizeof(f[mid])); f[mid][ed[mid].x][ed[mid].y]=f[mid][ed[mid].y][ed[mid].x]=ed[mid].c; for(int i=1;i<=n;i++) f[mid][i][i]=min(f[mid][i][i],ed[mid].r); for(int i=mid-1;i>=l;i--){ for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) f[i][j][k]=f[i+1][j][k]+ed[i].r; for(int j=1;j<=n;j++) f[i][ed[i].x][j]=min(f[i][ed[i].x][j],f[i+1][ed[i].y][j]+ed[i].c); for(int j=1;j<=n;j++) f[i][ed[i].y][j]=min(f[i][ed[i].y][j],f[i+1][ed[i].x][j]+ed[i].c); } memset(f[mid+1],inf,sizeof(f[mid+1])); f[mid+1][ed[mid+1].x][ed[mid+1].y]=f[mid+1][ed[mid+1].y][ed[mid+1].x]=ed[mid+1].c; for(int i=1;i<=n;i++) f[mid+1][i][i]=min(f[mid+1][i][i],ed[mid+1].r); for(int i=mid+2;i<=r;i++){ for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) f[i][j][k]=f[i-1][j][k]+ed[i].r; for(int j=1;j<=n;j++) f[i][j][ed[i].x]=min(f[i][j][ed[i].x],f[i-1][j][ed[i].y]+ed[i].c); for(int j=1;j<=n;j++) f[i][j][ed[i].y]=min(f[i][j][ed[i].y],f[i-1][j][ed[i].x]+ed[i].c); } for(int i=x;i<=y;i++){ if(q[i].l<=mid&&q[i].r>mid){ anss[q[i].id]=inf; for(int j=1;j<=n;j++) anss[q[i].id]=min(f[q[i].l][q[i].u][j]+f[q[i].r][j][q[i].v],anss[q[i].id]); } else if(q[i].r<=mid) d[++le]=q[i]; else d[--ri]=q[i]; } for(int i=x;i<=le;i++) q[i]=d[i]; for(int i=ri;i<=y;i++) q[i]=d[i]; solve(l,mid,x,le);solve(mid+1,r,ri,y); } int main(){ //freopen("a.in","r",stdin); n=R(),m=R(),Q=R();memset(anss,inf,sizeof(anss)); for(int i=1;i<=m;i++) ed[i].x=R(),ed[i].y=R(),ed[i].c=R(),ed[i].r=R(); for(int i=1;i<=Q;i++) q[i].u=R(),q[i].v=R(),q[i].l=R(),q[i].r=R(),q[i].id=i; solve(1,m,1,Q); for(int i=1;i<=Q;i++){ if(anss[i]==inf||anss[i]<0) cout<<"-1"<<endl; else cout<<anss[i]<<endl; } return 0; }