二分常用技巧

从小到大,升序排列

lower_bound(s,t,num):s-->t-1,查找到第一个大于或等于num的数的地址,不存在返回t

upper_bound(s,t,num):s-->t-1,查找到第一个大于num的数的地址,不存在返回t

从大到小,降序排列

lower_bound(s,t,num):s-->t-1,查找到第一个小于或等于num的数的地址,不存在返回t

upper_bound(s,t,num):s-->t-1,查找到第一个小于num的数的地址,不存在返回t

http://poj.org/problem?id=3061

求出总和不小于S的连续子序列的长度

普通方法

时间复杂度O(nlgn)

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
using namespace std;
int n,S;
int A[100010];
int sum[100010];///所有元素都大于0,所以是升序排列
void solve()
{
    for(int i=0; i<n; i++)
    {
        sum[i+1]=sum[i]+A[i];///sum[i]代表前i项的和
    }
    if(sum[n]<S)
    {
        printf("0\n");
        return;
    }
    int res=n;
    for(int s=0; sum[s]+S<=sum[n]; s++)//复杂度n,方法查找所有的(s,n]区间,直到区间和小于S
    {
        int t=lower_bound(sum+s,sum+n,sum[s]+S)-sum;//lgn
        res=min(res,t-s);
    }
    printf("%d\n",res);
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        memset(sum,0,sizeof(sum));
        scanf("%d%d",&n,&S);
        for(int i=0; i<n; i++)
            scanf("%d",&A[i]);
        solve();
    }
    return 0;
}

 

尺取法

更高效:  复杂度O(n)

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
using namespace std;
int n,S;
int A[100010];
void Scaling()
{
    int res=n+1;
    int s=0,t=0,sum=0;
    while(true)
    {
       while(t<n&&sum<S)//找到区间[s,t)大于或等于S
        sum+=A[t++];
       if(sum<S)break;//直到最后[s,n)的和小于S结束循环
       res=min(res,t-s);
       sum-=A[s++];//不断移动s,当区间[s,t)的和小于S时再往后移动t;
    }
    if(res>n)res=0;
    printf("%d\n",res);
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&S);
        for(int i=0; i<n; i++)
            scanf("%d",&A[i]);
        Scaling();
    }
    return 0;
}

http://poj.org/problem?id=3320

阅读其中连续的一些页把所有的知识点全都覆盖到

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
#include<set>
#include<map>
using namespace std;
int n;
int a[1000010];
map<int,int>Count;
void Scaling()
{
    set<int>all;
    for(int i=0; i<n; i++)
    {
        all.insert(a[i]);
    }
    int nn=all.size();//返回set容器中元素的个数(重复的元素不计算)
    int res=n;
    int s=0,t=0,num=0;
    while(true)
    {
        while(t<n&&num<nn)
        {
            if(Count[a[t++]]++==0)//先执行if(Count[a[t]]==0),然后再执行Count[a[t]]++,最后执行t++
            {
                num++;
            }
        }
        if(num<nn)
            break;
        res=min(res,t-s);
        if(--Count[a[s++]]==0)//先执行--Count[a[t]],然后再执行if(Count[a[t]]==0),最后执行s++
        {
            num--;
        }
    }
    printf("%d\n",res);
}
int main()
{
     scanf("%d",&n);
     for(int i=0;i<n;i++)
     {
         scanf("%d",&a[i]);
         Count[a[i]]=0;
     }
     Scaling();
     return 0;
}

反转

http://poj.org/problem?id=3276

N头牛,F面向前方,B面向后方,有一个机器刚开始就必须设定一个数值K,每次操作可以使K头连续的牛转向

为了能让牛都面向前方,求最少的操作数M和对应最小的K;

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
using namespace std;
int n;
int dir[5010];//牛的方向:F:0,B:1;
int f[5010];//区间[i,i+k-1]是否翻转
//固定k,求最小操作数
//无解返回-1
int calc(int k)
{
    memset(f,0,sizeof(f));
    int res=0;
    int sum=0;//记录i前面k-1个数的操作和
    for(int i=0;i+k<=n;i++)
    {
      //计算区间[i,i-k+1];
      if((dir[i]+sum)%2!=0)
      {
          res++;
          f[i]=1;
      }
      sum+=f[i];//记录i前面k-1个数的操作和
      if(i-k+1>=0)//当记录f的值大于k个就要减去多余的
        sum-=f[i-k+1];
    }
    for(int i=n-k+1;i<n;i++)//最后k-1数不能进行翻转操作,,
    {
        if((dir[i]+sum)%2!=0)
            return -1;
        if(i-k+1>=0)
            sum-=f[i-k+1];
    }
    return res;
}
void solve()
{
    int K=1,M=n;
    for(int k=1;k<=n;k++)//遍历1-n;
    {
        int m=calc(k);
        if(m>=0&&M>m)
        {
            M=m;
            K=k;
        }
    }
    printf("%d %d\n",K,M);
}
int main()
{
    scanf("%d",&n);
    char a[5];
    for(int i=0;i<n;i++)
    {
        scanf("%s",a);
        dir[i]=(a[0]=='B');
    }
    solve();
    return 0;
}

http://poj.org/problem?id=3279

有一个M*N的格子有正反两面一面是白色0,一面是黑色1,牛每次翻转一个格子,这个格子相邻的上下左右的格子都会翻转,要求牛将M*N个格子都翻成白色,求出用最小步数完成时每个格子的翻转的次数。最小步数有多个解时,输出字典序最小的一组,不存在的话输出IMPOSSIBLE;

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
using namespace std;
//邻接格子的坐标
const int dx[5]={-1,0,0,0,1};
const int dy[5]={0,-1,0,1,0};
//输入
int N,M;
int tile[20][20];

int opt[20][20];//保存最优解
int flip[20][20];//记录是否翻转
//查询(x,y)的颜色
int get(int x,int y)
{
    int c=tile[x][y];
    for(int i=0;i<5;i++)
    {
        int x2=x+dx[i],y2=y+dy[i];
        if(0<=x2&&x2<M&&0<=y2&&y2<N)
            c+=flip[x2][y2];
    }
    return c%2;
}
//求出第一行确定情况下的最小操作次数
//不存在返回-1
int calc()
{
    for(int i=1;i<M;i++)
    {
        for(int j=0;j<N;j++)
        {
            if(get(i-1,j)!=0)//如果(i-1,j)是黑色的话,这个地方必须翻转
                flip[i][j]=1;
        }
    }
    //判断最后一行是否全白
    for(int j=0;j<N;j++)
    {
        if(get(M-1,j)!=0)
            return -1;
    }
    //统计
    int res=0;
    for(int i=0;i<M;i++)
        for(int j=0;j<N;j++)
        res+=flip[i][j];
    return res;
}

void solve()
{
    int res=-1;
    //按照字典序遍历第一行所有的可能性---0,001,01,11,字典序---
    for(int i=0;i<1<<N;i++)
    {
        memset(flip,0,sizeof(flip));
        for(int j=0;j<N;j++)
            flip[0][N-j-1]=i>>j&1;
        int num=calc();
        if(num>=0&&(res<0||res>num))
        {
            res=num;
            memcpy(opt,flip,sizeof(flip));//储存最优解,将flip中的元素拷贝到opt中
        }
    }
    if(res<0)//无解
        printf("IMPOSSIBLE\n");
    else
    {
        for(int i=0;i<M;i++)
        {
            for(int j=0;j<N;j++)
            {
                printf("%d%c",opt[i][j],j+1==N?'\n':' ');
            }
        }
    }
}
int main()
{
    scanf("%d%d",&M,&N);
    for(int i=0;i<M;i++)
    {
        for(int j=0;j<N;j++)
        {
            scanf("%d",&tile[i][j]);
        }
    }
    solve();
    return 0;
}

弹性碰撞

http://poj.org/problem?id=3684

用N个半径为Rcm的球做实验在H米高的位置放了一个圆筒,实验开始最下面的球开始掉落,此后每一秒又有一个球掉落(不计空气阻力,所有的碰撞都是弹性碰撞),求实验开始的T秒中每个球底端的高度,(g==10.0m/s^2)

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
using namespace std;
const double g=10.0;
int N,H,R,T;
double y[110];//球的最终位置
double calc(int T)
{
    if(T<0)return H;
    double t=sqrt(2*H/g);
    int k=(int)T/t;
    if(k%2==0)//k为偶数
    {
        double d=T-k*t;
        return H-g*d*d/2.0;
    }
    else//k为奇数
    {
        double d=k*t+t-T;
        return H-g*d*d/2.0;
    }
}
void solve()
{
    for(int i=0;i<N;i++)
    {
        y[i]=calc(T-i);
    }
    sort(y,y+N);//假设球可以从球中间穿过去,彼此间相互不影响,求出y值,进行排序--最下面的球肯定最低(因为球是穿不过去的)---
    for(int i=0;i<N;i++)
    {
        printf("%.2lf%c",y[i]+2*R*i/100.0,i+1==N?'\n':' ');//第i个球的高度再加上2*R*i
    }
}
int main()
{
    int C;
    scanf("%d",&C);
    while(C--)
    {
       scanf("%d%d%d%d",&N,&H,&R,&T);
       solve();
    }
    return 0;
}

   折半枚举

给定各有n个整数的四个序列,A,B,C,D.要从每个序列中个取出一个数,使四个数的和为0.求出这样的组合个数.当一个数列中有多个相同的数字时,把它们作为不同的数字来看(1<=n<4000,|数字的值|<=2^28)

//如果暴力,时间为O(n^4)
//c+d=-a-b,将C,D中取数字的n^2中方法枚举出来,将这些和排好序,再用二分搜索,时间复杂度O(nlgn)
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
using namespace std;
const int MAX_N=4010;
int n;
int A[MAX_N],B[MAX_N],C[MAX_N],D[MAX_N];
int CD[MAX_N*MAX_N];

void solve()
{
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            CD[i*n+j]=C[i]+D[j];
        }
    }
    sort(CD,CD+n*n);

    long long res=0;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            int cd=-(A[i]+B[j]);
            //取出C和D中和为cd的部分
            res+=upper_bound(CD,CD+n*n,cd)-lower_bound(CD,CD+n*n,cd);
        }
    }
    printf("%lld\n",res);
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d%d%d%d",&A[i],&B[i],&C[i],&D[i]);
    }
    solve();
    return 0;
}

 超大背包问题(折半枚举)

有重量和价值分别为wi,vi的n个物品.从这些物品中挑选总重量不超过W的物品,求所有挑选方案中价值总和的最大值(1<=n<=40,1<=wi,vi<=10^15,1<=W<=10^15)

 

//因为价值和重量都很大,相比之下n比较小,但挑选物品的方法有2^n种不能直接枚举,但是把它拆成两半后再枚举的话,每部分只有20个是可行的
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
#include<string>
#include<vector>
#include<utility>
typedef long long ll;
using namespace std;
const int MAX_N=50;
const ll INF=1e18;
//输入
int n;
ll w[MAX_N],v[MAX_N];
ll W;

pair <ll,ll> ps[1<<MAX_N/2];//重量,价值;pair内设的是什么变量,里面就要写什么变量否则就会报错

void solve()
{
    //枚举前半部分
    int n2=n/2;
    for(int i=0;i<1<<n2;i++)
    {
        ll sw=0,sv=0;
        for(int j=0;j<n2;j++)
        {
            if(i>>j&1)
            {
                sw+=w[j];
                sv+=v[j];
            }
        }
        ps[i]=make_pair(sw,sv);
    }
    //去除多余元素,使重量和价值都从小到大排列
    sort(ps,ps+(1<<n2));//w重量按从小到大排列,若重量相等则v价值小的放前
    int m=1;
    for(int i=1;i<1<<n2;i++)
    {
        if(ps[m-1].second<ps[i].second)
            ps[m++]=ps[i];
    }
    //枚举后半部分并求解
    ll res=0;
    for(int i=0;i<1<<(n-n2);i++)
    {
        ll sw=0,sv=0;
        for(int j=0;j<n-n2;j++)
        {
            if(i>>j&1)
            {
                sw+=w[n2+j];
                sv+=v[n2+j];
            }
        }
        if(sw<=W)
        {
            ll tv=(lower_bound(ps,ps+m,make_pair(W-sw,INF))-1)->second;//找到重量相等,价值相等的话就会返回这个地址,否则就会返回第一个大于重量的地址
            res=max(res,sv+tv);
        }
    }
    printf("%lld\n",res);
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%lld%lld",&w[i],&v[i]);
    }
    scanf("%lld",&W);
    solve();
}

       坐标离散化

w*h的格子上画了n条垂直或水平的宽度为1的直线,求出这些线将格子划分成了多少个区域(x是列,y是行)

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#include <time.h>
#include<math.h>
#include<string>
#include<vector>
#include<utility>
using namespace std;
const int maxn=510;
const int dx[4]={0,0,1,-1};
const int dy[4]={-1,1,0,0};
//输入
int W,H,N;
int X1[maxn],X2[maxn],Y1[maxn],Y2[maxn];
//标记
bool fld[maxn*6][maxn*6];
//对x1,x2进行坐标离散化,并返回离散化之后的宽度
int compress(int *X1,int *X2,int w)
{
    vector<int> xs;

    for(int i=0; i<N; i++)
    {
        for(int d=-1; d<=1; d++)//保留x1,x2和他们两侧的坐标
        {
            int tx1=X1[i]+d,tx2=X2[i]+d;
            if(1<=tx1&&tx1<=W)
                xs.push_back(tx1);
            if(1<=tx2&&tx2<=W)
                xs.push_back(tx2);
        }
    }
    sort(xs.begin(),xs.end());
    //去重
    xs.erase(unique(xs.begin(),xs.end()),xs.end());/*unique去除相邻的重复元素,只保
    留一个但并不是将重复原素删除,而是将它们移到了后面,返回的迭代器指向超出无重复元素范围末端的下一个位置*/
    //将x1,x2化为离散化后的坐标
    for(int i=0; i<N; i++)
    {
        X1[i]=find(xs.begin(),xs.end(),X1[i])-xs.begin();
        X2[i]=find(xs.begin(),xs.end(),X2[i])-xs.begin();
    }
    return xs.size();//离散化后的宽度
}

void solve()
{
    //坐标离散化
    W=compress(X1,X2,W);
    H=compress(Y1,Y2,H);
    //标记有直线的部分
    memset(fld,0,sizeof(fld));
    for(int i=0;i<N;i++)
    {
        for(int y=Y1[i];y<=Y2[i];y++)
        {
            for(int x=X1[i];x<=X2[i];x++)
            {
                fld[y][x]=true;
            }
        }
    }
    //求区间个数
    int ans=0;
    for(int y=0;y<H;y++)
    {
        for(int x=0;x<W;x++)
        {

            if(fld[y][x])continue;
            ans++;
            //宽度优先搜索
            queue<pair<int,int> >que;
            que.push(make_pair(x,y));
            while(!que.empty())
            {
                int sx=que.front().first,sy=que.front().second;
                que.pop();

                for(int i=0;i<4;i++)
                {
                    int tx=sx+dx[i],ty=sy+dy[i];
                    if(tx<0||tx>=W||ty<0||ty>=H)
                        continue;
                    if(fld[ty][tx])continue;
                    que.push(make_pair(tx,ty));
                    fld[ty][tx]=true;
                }
            }
        }
    }
    printf("%d\n",ans);
}
int main()
{
    scanf("%d%d%d",&W,&H,&N);
    for(int i=0;i<N;i++)
    {
        scanf("%d%d%d%d",&X1[i],&X2[i],&Y1[i],&Y2[i]);
    }
    solve();
}
//10 10 5
//1 6 4 4
//1 10 8 8
//4 4 1 10
//9 9 1 5
//10 10 6 10

 

                                                       

                  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

                  

 

 

 

 

 

 

 

 

 

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值