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;}
原创粉丝点击