Noip2011提高组总结
这套题思考的难度比较大,应该说是有四题基础题,一题比较复杂的搜索加模拟,还有一题需要深度思考一下。自己的代码漏洞还是很大,而且思考的时候会遗漏一些情况,这些错误都是致命的,去年Noip的惨败也证实了这一点,许多时候,我并没有败在算法上,而是细节与心态上。记住犯过的错误,尽力不在同一个地方摔倒,那么才可能不断进步,否则一直都在原地踏步。
DAY1 》
T1:铺地毯
枚举然后判断,就是考验基础的代码能力
#include <cstdio> int x,y,ans,n,h[10005],t[10005],l[10005],r[10005]; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d%d%d%d",&h[i],&l[i],&t[i],&r[i]); ans=-1; scanf("%d%d",&x,&y); for(int i=1;i<=n;i++)if(h[i]<=x&&(x<=h[i]+t[i])&&l[i]<=y&&(y<=r[i]+l[i]))ans=i; printf("%d\n",ans); return 0; }
T2:选择客栈
首先这还是一道枚举题,边读入边处理出每种颜色的前缀和,同时记录上一个满足最低消费要求的咖啡馆的位置,每次加上这个位置的同颜色前缀和即可,复杂度O(kn)。需要注意的是答案超过了int的范围,需要开long long,而且一开始做这道题是忽略了可以在自己的旅馆喝咖啡的情况,这是不应该的。
#include <cstdio> #include <iostream> using namespace std; const int N=200005; long long sum; int pr,n,k,pm,pn,co,s[N][55]; int main(){ scanf("%d%d%d",&n,&k,&pm); for(int i=1;i<=n;i++){ scanf("%d%d",&co,&pn); for(int j=0;j<k;j++)s[i][j]=s[i-1][j]; s[i][co]++; if(pn<=pm)pr=i; if(pr!=i)sum+=(long long)s[pr][co]; else sum+=(long long)s[pr-1][co]; } cout<<sum<<endl; return 0; }
T3:Mayan游戏
直接模拟题目中的坠落,清除和操作的过程,直接按操作的字典序搜索,特别注意相同的两个块不要移动,不同的块不要反复交换,此外要有一个判断,当剩余的彩块有一种没到3个时直接剪枝,因为这种情况一定是无法完成的,然后就是一些自己程序的细节问题要多注意。
#include <cstdio> #include <cstring> #include <algotithm> using namespace std; const int maxx=10,maxy=12,maxc=15; int n,c,a[maxx][maxy],cnt[maxc],ans[maxx][3]; bool f[maxx][maxy]; void fall(int x){ for(int i=0;i<7;i++){ if(a[x][i]==0){ int j=i+1; while(j<7&&a[x][j]==0)j++; if(j==7)return; else swap(a[x][i],a[x][j]); } } } bool clear(){ bool flag=false; for(int i=0;i<5;i++) for(int j=0;j<7;j++){ if(!a[i][j]) continue; if(i<3&&a[i][j]==a[i+1][j]&&a[i][j]==a[i+2][j]) {f[i][j]=1;f[i+1][j]=1;f[i+2][j]=1;} if(j<5&&a[i][j]==a[i][j+1]&&a[i][j]==a[i][j+2]) {f[i][j]=1;f[i][j+1]=1;f[i][j+2]=1;} } for(int i=0;i<5;i++) for(int j=0;a[i][j]&&j<7;j++)if(f[i][j]){ flag=1; cnt[a[i][j]]--; a[i][j]=f[i][j]=0; } for(int i=0;i<5;i++)fall(i); return flag; } int check(){ int minc=0; for(int i=1;i<=c;i++)if(cnt[i]) if(!minc||minc>cnt[i])minc=cnt[i]; return minc; } void print(){ for (int i=1;i<=n;i++) printf("%d %d %d\n",ans[i][0],ans[i][1],ans[i][2]); exit(0); } void dfs(int move){ int tmpd[maxx][maxy],tmpdc[maxc]; for(int i=0;i<5;i++)for(int j=0;j<7;j++)tmpd[i][j]=a[i][j]; for(int i=1;i<=c;i++)tmpdc[i]=cnt[i]; for (int i=0;i<5;i++) for(int j=0;a[i][j]&&j<7;j++) for(int k=1;k>=-1;k-=2) if(i+k>=0&&i+k<5){ if((k==-1&&a[i-1][j])||a[i][j]==a[i+k][j])continue; ans[move][0]=i; ans[move][1]=j; ans[move][2]=k; swap(a[i][j],a[i+k][j]); fall(i); fall(i+k); while(clear()); int tmp=check(); if(move==n){if(tmp==0)print();} else if(tmp>2)dfs(move+1); for(int i=0;i<5;i++) for(int j=0;j<7;j++)a[i][j]=tmpd[i][j]; for(int i=1;i<=c;i++)cnt[i]=tmpdc[i]; } } int main(){ scanf("%d",&n); int tmp; c=0; for(int i=0;i<5;i++) for(int j=0;j<=7;j++){ scanf("%d",&tmp); if(tmp==0)break; a[i][j]=tmp; c=max(c,tmp); cnt[tmp]++; }dfs(1); printf("-1\n"); return 0; }
DAY2 》
T1:计算系数
基础的二项式展开,先计算出组合数,然后乘上两个数幂即可。
#include <iostream> typedef long long LL; const int mod=10007; using namespace std; LL ans,a,b,l,k,m,n,c[1500][1500]; LL power(LL a,LL b,LL m){ LL t,y; t=1; y=a; while(b){ if(b&1)t=t*y%m; y=y*y%m; b>>=1; }return t; } int main(){ cin>>a>>b>>k>>n>>m; ans=1; c[1][1]=1; for(int i=2;i<=k+1;i++) for(int j=1;j<=i;j++)c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod; ans=c[k+1][m+1]; ans=ans*power(a,n,mod)%mod; ans=ans*power(b,m,mod)%mod; cout<<ans<<endl; return 0; }
T2:聪明的质检员
由题目可得,根据 w 取值的变化,检验结果具有单调性,所以我们二分答案 w,然后对于 check 的书写,可采用前缀和,记录i 位之前有几个大于 w,即用来计算 l,同时,记录下大于 w 的 v 前缀和,之后用 check 与 s 取差,不断更新最小值。
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> using namespace std; typedef long long LL; const int N=200005; char c; LL wmax,wmin,ans,tmp,s1,w[N],v[N],h[N],s[N]; int i,j,k,l,m,n,r,mid,r1[N],l1[N]; LL check(LL x){ int i,j; LL tmp; memset(h,sizeof(h),0); memset(s,sizeof(s),0); h[0]=s[0]=tmp=0; for(int i=1;i<=n;i++)if(w[i]>=x) {s[i]=s[i-1]+v[i];h[i]=h[i-1]+1;} else{s[i]=s[i-1];h[i]=h[i-1];} for(int i=1;i<=m;i++) tmp=tmp+(LL)((s[r1[i]]-s[l1[i]-1])*(h[r1[i]]-h[l1[i]-1])); return tmp; } int scan(LL &x){ while(c=getchar(),c<'0'||c>'9');x=c-'0'; while(c=getchar(),c>='0'&&c<='9')x=x*10+c-'0'; } int main(){ cin>>n>>m>>s1; wmax=0; wmin=~0U>>1; for(int i=1;i<=n;i++){ scan(w[i]);scan(v[i]); if(w[i]>wmax)wmax=w[i]; if(w[i]<wmin)wmin=w[i]; } for(int i=1;i<=m;i++)scanf("%d%d",&l1[i],&r1[i]); l=wmin;r=wmax+1; ans=abs(check(l)-s1); do{ mid=(l+r)>>1; tmp=check(mid); if(tmp==s1){ans=0;break;} if(tmp<s1){ans=min(ans,abs(tmp-s1));r=mid-1;} if(tmp>s1){ans=min(ans,abs(tmp-s1));l=mid+1;} }while(l<=r); cout<<ans<<endl; return 0; }
T3:观光公交
这道题需要先求出不用加速器到达某站需要的总时间,这个很好求,就是每次到达前一站最后一个乘客的时间与到前一站的实际时间的最大值加上中间路程所需要的时间,最终时间记为ans.
然后考虑加速器对时间的影响,采用贪心策略,每个加速器都要用在能使最多人减少时间的地方,需要注意的是,并不是当前在车上的所有人都可以减少时间,因为在如果使用加速器加速后提前到了某站,车上的乘客还是需要在此地等待,时间不变。所以如果在i+1站有会产生等待时间的话,我们在i到i + 1间使用加速器,到i + 1站的乘客的旅行时间都会减一,别的不变,如果没有等待时间,所有人的时间都会减一,所以分情况讨论,计算出每一站公交不在等待上浪费时间最远可以到达的站点,每次求出最大值,在ans中减去,同时维护时间和最远可达站,最后就是答案。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; struct node{int st,ar,ta;}a[20000]; int n,m,K,ans,f[20000],at[20000],next[20000],t[20000],sum[20000]; int main(){ scanf("%d%d%d",&n,&m,&K); for(int i=1;i<n;i++)scanf("%d",&t[i]); for(int i=1;i<=m;i++){ scanf("%d%d%d",&a[i].ar,&a[i].st,&a[i].ta); f[a[i].st]=max(f[a[i].st],a[i].ar); sum[a[i].ta]++; }at[1]=0; for(int i=2;i<=n;i++)sum[i]+=sum[i-1]; for(int i=2;i<=n;i++)at[i]=max(at[i-1],f[i-1])+t[i-1]; for(int i=1;i<=m;i++)ans+=at[a[i].ta]-a[i].ar; while(K){ next[n]=next[n-1]=n; for(int i=n-2;i;i--){ if(at[i+1]<=f[i+1])next[i]=i+1; else next[i]=next[i+1]; } int maxn=0,j; for(int i=1;i<=n;i++) if(sum[next[i]]-sum[i]>maxn&&t[i]>0)maxn=sum[next[i]]-sum[i],j=i; if(!maxn)break; ans-=maxn,t[j]--,K--; at[1]=0; for(int i=2;i<=n;i++)at[i]=max(at[i-1],f[i-1])+t[i-1]; } printf("%d\n",ans); return 0; }
注意点:
1.注意数据范围是否超过int,若是需要开long long;
2.注意C++过程中数组不能开过大,大数组要声明在主程序中,在过程中注意要初始化一下;
3.读入优化很重要,如果时间允许最好都做一个读入优化;
4.读题仔细,注意多种情况和一些细枝末节的条件,分类讨论要思考清楚。
愿你出走半生,归来仍是少年