8.4 暑假集训——常用方法集锦
来源:互联网 发布:淘宝会员等级有什么用 编辑:程序博客网 时间:2024/05/19 19:40
这场题主要涉及尺取法、开关问题、弹性碰撞、折半枚举、离散化、前缀和等技巧的运用,可以参考《挑战程序设计》3.3.2章常用技巧精选
有些打星号的题目可以重点回顾一下
这些题只是大概覆盖了一些这些技巧,具体深入的运用还需要更多的练习,技巧最好要能更加灵活的想到和使用~ 干巴爹吧继续在刷题的道路上越走越远2333
前缀和
sum
题意: 给一段长为n的数组,判断是否存在一段连续子序列和可以被m整除,n,m已知
思路: 处理出来所有的前缀和%m的值,如果有两个前缀和%m的值相同,即存在连续的一段子序列,举个例子,当n=3,m=3
数列 1 2 3
前缀和 1%3==1 3%3==0 6%3==0
可知S3==S2(mod m), 所以 (S3-S2)%m==0 (取余的性质) 所以存在子序列 3 满足条件
#include <iostream>#include <cstdio>#include <cstring>#include <map>#include <string>using namespace std;map<int,int> mark;int a[100010];int per[100010];int main(){ int n,m; int t; scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); memset(a,0,sizeof(a)); memset(per,0,sizeof(per)); mark.clear(); //记录每个Si%m 的值,如果出现过则YES mark[0]=1; bool res=false; for(int i=0;i<n;i++){ scanf("%d",&a[i]); per[i]= i==0? a[i]:per[i-1]+a[i]; //per 为前缀和 per[i]%=m; //取余 if(mark.count(per[i])) res=true; //判断 else mark[per[i]]=1; } if(res) printf("YES\n"); else printf("NO\n"); } return 0;}
尺取法
Graveyard Design
题意: 求一段连续自然数,每个数的平方之和等于n,其中1<=n<=1e14,如果有多个解,按个数递减输出
思路: 算是裸的尺取法的题目吧,如果和小于n,则r++,否则l++
#include <iostream>#include <cstdio>#include <cstring>#include <string>#include <vector>#include <map>using namespace std;typedef long long LL;LL n;vector<int> res;void solve(){ //尺取过程 LL l,r; l=r=1; LL sum=1; while(r*r<=n){ if(sum==n){ res.push_back(l); res.push_back(r); sum-=l*l; l++; } else if(sum<n){ r++; sum+=r*r; } else if(sum>n){ sum-=l*l; l++; } } return;}int main(){ while(~scanf("%lld",&n)){ res.clear(); solve(); if(res.empty()) printf("0\n"); else{ printf("%d\n",res.size()/2); for(int i=0;i<res.size();i+=2){ //输出 printf("%d",res[i+1]-res[i]+1); for(int j=res[i];j<=res[i+1];j++) printf(" %d",j); printf("\n"); } } } return 0;}
Finding Seats****
题意: 找出面积最小的矩阵,使其包含的‘.’的个数不小于k,输出最小的面积
思路: 尺取法,注意列可以尺取,行的话需要套两个循环(想一想,为什么)
#include <iostream>#include <cstdio>#include <string>#include <cstring>using namespace std;int a[310][310];int row[310];int cow[310][310];int main(){ int r,c,k; while(~scanf("%d%d%d",&r,&c,&k)&&r){ memset(a,0,sizeof(a)); memset(cow,0,sizeof(cow)); memset(row,0,sizeof(row)); char s[310]; for(int i=0;i<r;i++){ scanf("%s",s); for(int j=0;j<c;j++){ if(s[j]=='.') a[i][j]=1; else a[i][j]=0; row[i]+=a[i][j]; //记录行中'.'个数 cow[i][j]= i==0? a[i][j]:cow[i-1][j]+a[i][j]; } } int ans=1e9; int sumr=row[0]; int i,ii; i=ii=0; for(i=0;i<r;i++) for(ii=0;ii<r;ii++){ int sum,j,jj; j=jj=0; sum= i==0? cow[ii][j]:cow[ii][j]-cow[i-1][j]; while(jj<c){ if(sum<k){ jj++; sum+= i==0? cow[ii][jj]:cow[ii][jj]-cow[i-1][jj]; } else{ ans=min(ans,(ii-i+1)*(jj-j+1)); sum-=(i==0? cow[ii][j]:cow[ii][j]-cow[i-1][j]); j++; } } } printf("%d\n",ans); } return 0;}
EXTENDED LIGHTS OUT****
题意: 给一个5*6 的01矩阵,每对一个数翻转,它的上下左右的四个数都会翻转(边界同理),求出一种可能的操作使矩阵都变为1
思路: 处理出第一行数的所有操作,则接下来所有行的操作都可以确定了,若最后一行最后也都翻转为1,则可行
注意: 每一行的数的决策被三个因素影响: 它本身,上一行对应位置的操作,上一行对应的数
#include <iostream>#include <cstdio>#include <cstring>#include <cstdio>#include <algorithm>#include <cmath>using namespace std;int a[10][10];int op[10][10];int main(){ int t; scanf("%d",&t); int cas=1; while(t--){ memset(a,0,sizeof(a)); memset(op,0,sizeof(op)); for(int i=0;i<5;i++) for(int j=0;j<6;j++) scanf("%d",&a[i][j]); for(int i=0;i<(1<<6);i++){ int ii=i; for(int j=5;j>=0;j--){ op[0][j]=ii&1; ii>>=1; } for(int r=1;r<5;r++){ for(int c=0;c<6;c++){ int state; if(c==0) state=op[r-1][c]+op[r-1][c+1]; else if(c==5) state=op[r-1][c-1]+op[r-1][c]; else state=op[r-1][c-1]+op[r-1][c]+op[r-1][c+1]; if(r>=2) state+=op[r-2][c]; op[r][c]=(state+a[r-1][c])%2; } } bool res=true; for(int c=0;c<6;c++){ int state; if(c==0) state=op[4][c]+op[4][c+1]; else if(c==5) state=op[4][c-1]+op[4][c]; else state=op[4][c-1]+op[4][c]+op[4][c+1]; if((state+a[4][c]+op[3][c])%2==1) res=false; } if(res) { break; } } printf("PUZZLE #%d\n",cas++); for(int i=0;i<5;i++){ for(int j=0;j<6;j++){ if(j) printf(" "); printf("%d",op[i][j]); } printf("\n"); } } return 0;}
开关问题
The Water Bowls ****
题意: 有20个0或1的数,现有翻转操作,每次翻转一个数和它左右两侧的数,(两个端点只翻转一侧),求最少的操作使其全部翻转为0
思路: 如果前三个数字的翻转方式确定下来,那么其它1的翻转都可以确定,前三个数要么先一次改变a1,a2,要么一次a1,a2,a3全部改变,所以只要两种方法都使一下,选出最优的那个即可,注意可能一种翻转是无解的
#include <iostream>#include <cstdio>#include <cstring>#include <string>using namespace std;int a[25];int b[25];int main(){ while(~scanf("%d",&a[0])) { for(int i=1;i<20;i++) scanf("%d",&a[i]); for(int i=0;i<20;i++) b[i]=a[i]; int res=1; a[1]=!a[1]; a[0]=!a[0]; for(int i=0;i<=18;i++){ //第一种翻转 if(a[i]==1){ a[i]=!a[i]; res++; if(i==18) a[19]=!a[19]; else{ a[i+1]=!a[i+1]; a[i+2]=!a[i+2]; } } } if(a[19]==1) res=1e9; //若无解 int ans=0; for(int i=0;i<=18;i++){ //第二种翻转 if(b[i]==1){ ans++; b[i]=!b[i]; if(i==18) b[19]=!b[19]; else{ b[i+1]=!b[i+1]; b[i+2]=!b[i+2]; } } } if(b[19]==1) ans=1e9; //若无解 res=min(res,ans); printf("%d\n",res); } return 0;}
折半法
Sumsets
题意: 给一串数组,找出不同的四个数a,b,c,d,使a+b+c=d,输出d最大的那组解
思路: 先暴力处理所有的a+b,再暴力d,c,看d-c是否出现过,注意怎么保证a,b,c,d 四个数字不重复
#include <iostream>#include <cstdio>#include <cstring>#include <string>#include <algorithm>#include <cmath>#include <vector>#include <map>using namespace std;map<int,int> mark;int a[1010];const int INF=1e9;//一开始用的两个map 一直T, 后来看了别人的思路才想起来有结构体这回事…… 是做题做傻了吗……struct plu{ int a,b; int tot; plu(int a,int b,int tot):a(a),b(b),tot(tot) {};};bool operator < (const plu& a,const plu& b){ // 想一下为什么要这么定义 <号 if(a.tot!=b.tot) return a.tot<b.tot; return a.a==b.a||a.b==b.a||a.a==b.b||a.b==b.b;}vector<plu> vec;int main(){ int n; while(~scanf("%d",&n)&&n){ vec.clear(); memset(a,0,sizeof(a)); for(int i=0;i<n;i++){ scanf("%d",&a[i]); for(int j=0;j<i;j++){ vec.push_back(plu(a[i],a[j],a[i]+a[j])); //把原来的map换成的vector } } sort(vec.begin(),vec.end()); sort(a,a+n); bool res=false; int ans=INF; for(int i=n-1;i>=0;i--){ for(int j=n-1;j>=0;j--){ if(i==j) continue; int num=a[i]-a[j]; //a[i]为d,a[j]为a,num=d-a,判断是否存在b+c=num? vector<plu>::iterator it=lower_bound(vec.begin(),vec.end(),plu(a[i],a[j],num)); if(it!=vec.end()&&(*it).tot==num){ printf("%d\n",a[i]); res=true; break; } } if(res) break; } if(res==false) printf("no solution\n"); } return 0;}
弹性碰撞*******
Linear world
题意: 长为l的数轴,已知n只蚂蚁的位置,朝向和名字,每次两只蚂蚁碰到它们会朝反方向前进,速度都为v,求最后一只掉下去的蚂蚁的名字和时间
思路: 因为速度都一样,可以看做每只蚂蚁一直沿原方向爬行,可以分别知道左右两边掉下去的蚂蚁的数目,和最后一只蚂蚁是从哪一段掉下去的
回到这题,由于两边的蚂蚁碰撞后肯定不能越过中间的蚂蚁从另一端掉下去,所以可以分别知道从两端有哪些蚂蚁掉下去,找到中间的那个就好
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#include <cmath>#include <vector>#include <string>using namespace std;struct ants{ string name; int dir; double pos; ants(int d=0,double p=0,string n=""):dir(d),pos(p),name(n) {};}a[33000];int main(){ double l,v; int nn; while(~scanf("%d",&nn)&&nn){ scanf("%lf%lf",&l,&v); int p,n; p=n=0; bool res; double dist=0; for(int i=0;i<nn;i++){ char d[3]; scanf("%s%lf",d,&a[i].pos); getline(cin,a[i].name); if(d[0]=='p'||d[0]=='P') {p++; a[i].dir=1;} else {n++; a[i].dir=0;} if(a[i].dir==1&&dist<l-a[i].pos){ dist=l-a[i].pos; res=true; } else if(a[i].dir==0&&dist<a[i].pos){ dist=a[i].pos; res=false; } } int num; if(res) num=nn-p; else num=n-1; printf("%13.2lf ",(double)((int((dist/v)*100.0))/100.0)); //小数点直接截取,否则为四舍五入, 注意g++和c++交的区别, int i=0,j=a[num].name.size()-1; while(a[num].name[i]==' ') i++; while(a[num].name[j]==' ') j--; for(;i<=j;i++) printf("%c",a[num].name[i]); cout<<endl; } return 0;}
- 8.4 暑假集训——常用方法集锦
- ACM暑假集训方法
- 暑假集训——破壳计划
- 2016暑假集训总结——Part1
- 2016暑假集训总结——Part2
- XYNU—ACM暑假集训第一次测试
- XYNU—ACM暑假集训第二次测试
- 暑假集训日记--8.4--搜索
- 暑假集训——贪心专题——F题
- 暑假集训——贪心专题——A题
- 暑假集训——贪心专题——D题
- 暑假集训——贪心专题——I题
- 暑假集训——贪心专题——E题
- 暑假集训——贪心专题——C题
- ACM 学习心得 ——2014年ACM暑假集训有感
- 暑假集训第二周——贪心 盒子平移
- 暑假集训第二周——贪心 L - 生物碰撞
- 暑假集训第二周——贪心 F - 削木棒
- 使用C#发送Http请求实现模拟登陆实例
- 设计模式——单列设计模式
- [直观学习排序算法] 视觉直观感受若干常用排序算法
- POJ
- SpringMVC H5 js摄像头拍照 ajax上传返回url路径
- 8.4 暑假集训——常用方法集锦
- cookie和session详解
- POJ
- HttpClient 设置代理 及Fiddler查看请求
- 常用排序算法总结
- Java入门笔记-(常见工具类)
- CentOS 6.8 新安装系统的网络IP配置(转载)
- poj3233 Matrix Power Series
- 啥是市场调查公司--Ipsos和Sterling Brands