这场题主要涉及尺取法、开关问题、弹性碰撞、折半枚举、离散化、前缀和等技巧的运用,可以参考《挑战程序设计》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;
}