第一章:模拟、枚举和贪心(7.16、7.17)
枚举
NC16593 [NOIP2011]铺地毯
直接for找到最大的编号即可
前缀和和差分
NC16649 校门外的树
差分数组统计sum,或者也可以用区间覆盖贪心的思想去做
尺取法(双指针)
NC18386 字符串
记录l和r,要求包括所有小写字母,先r右移到满足条件,再l左移,最终取最小的ans
NC207040 丢手绢
距离为顺时针或者逆时针取min
所以离得最远的两个小朋友的距离<=圆周长的一版
考虑圆上的尺取法 i和j的顺时针距离(输入也是给的顺时针)>=周长一半的时候更新判断ans
#include<bits/stdc++.h> using namespace std; int a[100003]; int main() { int n;scanf("%d",&n); int all=0; for(int i=1;i<=n;++i) { scanf("%d",&a[i]); all+=a[i]; } int x=all/2; int ans=0; int sum=0; int l=1; for(int i=1;i<=n;++i) { while(sum<x) { l%=n; if(!l)l=n; sum+=a[l]; l++; } ans=max(ans,min(sum,all-sum)); sum-=a[i]; } printf("%d\n",ans); }
枚举+递推
NC20241 [SCOI2005]扫雷MINE
只有两列的扫雷,设a[i]为第二列i位置的数,f[i]为第一列i位置是否有雷(0/1)
可得a[i]=f[i+1]+f[i]+f[i-1] (a[1]和a[n]特殊判断)
递推得f[i+1],然后分情况枚举
#include<bits/stdc++.h> #define LL long long using namespace std; int a[10003],f[10003]; LL ans=0; int n; int work()//a[i]=f[i]+f[i-1]+f[i+1] { for(int i=2;i<n;++i) { int t=a[i]-f[i]-f[i-1]; if(t<0||t>2)return 0; f[i+1]=t; } if(f[n]+f[n-1]!=a[n])return 0; return 1; } int main() { scanf("%d",&n); for(int i=1;i<=n;++i)scanf("%d",&a[i]); if(a[1]>2||a[n]>2){printf("0\n");return 0;} if(a[1]==2) { f[1]=1;f[2]=1; ans+=work(); } else if(a[1]==0) { f[1]=0;f[2]=0; ans+=work(); } else if(a[1]==1) { f[1]=1;f[2]=0; ans+=work(); memset(f,0,sizeof(f)); f[1]=0;f[2]=1; ans+=work(); } printf("%lld\n",ans); }
NC235250 牛可乐的翻转游戏
(待补)
指针优化(预处理数组往后跳)
NC23053 月月查华华的手机
注意是子序列而不是子串(否则直接kmp)
所以处理nextt[i][j]数组,表示在i的右边,j+'a'的第一个位置(相当于贪心去往后跳)
#include<bits/stdc++.h> #define LL long long using namespace std; char A[1000003],b[1000003]; int nextt[1000003][30]; int main() { scanf("%s",A+1); int l=strlen(A+1); for(int i=l-1;i>=0;--i) { for(int j=0;j<26;++j)nextt[i][j]=nextt[i+1][j]; nextt[i][A[i+1]-'a']=i+1; } int T;scanf("%d",&T); while(T--) { scanf("%s",b+1); int l2=strlen(b+1); int k=0,flagg=0; for(int i=1;i<=l2;++i) { if(nextt[k][b[i]-'a'])k=nextt[k][b[i]-'a']; else{flagg=1;break;} } if(flagg)printf("No\n"); else printf("Yes\n"); } }
贪心
一般贪心
NC23036 华华听月月唱歌
区间覆盖问题(注意超过n了break掉,这个没注意到调了好久)
#include<bits/stdc++.h> #define LL long long using namespace std; struct qj{ int l,r; }w[100003]; bool cmp(qj a,qj b) { if(a.l==b.l)return a.r>b.r; return a.l<b.l; } int main() { int n,m;scanf("%d%d",&n,&m); for(int i=1;i<=m;++i)scanf("%d%d",&w[i].l,&w[i].r); sort(w+1,w+1+m,cmp); int l=w[1].l,r=w[1].r; w[m+1].l=0x3f3f3f3f; if(l!=1){printf("-1\n");return 0;} int ans=1; for(int i=2;i<=m;++i) { if(w[i].l>=l&&w[i].r<=r)continue; if(w[i].l>r+1){printf("-1\n");return 0;} int R=r; while(w[i].l<=r+1&&i<=m) { R=max(R,w[i].r); i++; } i--; if(R>r)r=R,ans++; if(r>=n)break; } if(r<n)printf("-1\n"); else printf("%d\n",ans); }
推公式+贪心
NC16561 国王的游戏
以下是牛客的题解:
我们对于国王身后的两个点来分析
队列可能是这样的:
* | Left | Right |
---|---|---|
king: | a0 | b0 |
p1 | a1 | b1 |
p2 | a2 | b2 |
那么我们计算可得ans1=max(a0/b1,a0×a1/b2)
队列也有可能是这样的
* | Left | Right |
---|---|---|
king: | a0 | b0 |
p2 | a2 | b2 |
p1 | a1 | b1 |
那么我们计算可得ans2=max(a0/b2,a0×a2/b1)
我们来对比一下两个答案:
ans1=max(a0/b1,a0×a1/b2)
ans2=max(a0/b2,a0×a2/b1)
可以替换得:
ans1=max(k1,k2)
ans2=max(k3,k4)
显然我们可以得到:
a0×a1/b2>a0/b2
a0×a2/b1>a0/b1
即:k2>k3 k4>k1
如果ans1<ans2
那么易得:
k4>k2
即: a0×a2/b1>a0×a1/b2
变形可得:
a1×b1<a2×b2
当a1×b1<a2×b2时,我们也能够得到ans1<ans2的结论
所以,为了ans取到最小值,我们需要将ai×bi较小的放在前面
那么我们以ai×bi为关键字排序即可
同时要写高精度
按位贪心
NC18979 毒瘤xor
对于每一位去看所有的数,如果为1的数多于为0的数,此位取0,否则取1
所以问题转化为统计每一位上有多少个数是0,多少个数是1
每次询问区间,考虑前缀和进行处理
#include<bits/stdc++.h> #define LL long long using namespace std; int sum[100003][35]; int main() { int n;scanf("%d",&n); for(int i=1;i<=n;++i) { int a;scanf("%d",&a); for(int j=0;j<31;++j) { if(a&1)sum[i][j]=sum[i-1][j]+1; else sum[i][j]=sum[i-1][j]; a>>=1; } } int Q;scanf("%d",&Q); while(Q--) { int l,r;scanf("%d%d",&l,&r); int x=0,all=r-l+1; for(int i=0;i<31;++i) { int k=sum[r][i]-sum[l-1][i]; if(k<all-k)x|=(1<<i); } printf("%d\n",x); } }