8.4 暑假集训——常用方法集锦

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值