二分和前缀和

二分和前缀和
1.二分

解可以从一段区间内得出,且排除解时,该区间具有二段性(举例,若某位置不是解,那么比他大的都不是解

1.1整数二分

if条件后接r=mid,无处理;接l=mid,mid=l+r+1 >>1.两个模板.

int l=0,r=n-1;//确定区间范围
while(l<r)
{
    int mid=l+r >>1;
    if(q[mid]>=x) r=mid;
    else l=mid+1;
}
 while(l<r)
 {
     int mid=l+r+1 >> 1;
     if(q[mid]<=x ) l=mid;
     else
     r=mid-1;
 }

730. 机器人跳跃问题

最坏的情况就是柱子排列是降序的,一直在减少能量,算出最多需要的能量limit,在1~limit之间二分

#include<iostream>
#include<cstdio>
#include<cstring> 
#include<vector>
#include<algorithm>

using namespace std;

int n;
vector<int> H;
int maxH;

bool check(int mid)
{
	for(int i=0;i<H.size();i++)
	{
	    mid=2*mid-H[i];
		if(mid>=maxH)  return true;
		if(mid<0) return false;
	}
	
     return true;

}
int main()
{
	cin>>n;

	int limit=0;
	for(int i=0;i<n;i++)
	{
		int temp;
		scanf("%d",&temp);
		H.push_back(temp);
		limit+=temp;
	}
	
    maxH=*max_element(H.begin(),H.end());
	int l=0,r=limit;
	
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(check(mid))
		{
			r=mid;
		}
		else{
			l=mid+1;
		}
	} 
	cout<<r<<endl;
	return 0;
}

1221. 四平方和

1.暴力枚举,dfs(三种搜索模式中的排列型枚举+可以重复值)或者三重循环

循环枚举
int main()
{
    cin>>n;
    for( int a=0;a*a<= n; a++)
        for(int b=a; a*a+b*b<=n; b++)
            for(int c=b; a*a+b*b+c*c <=n; c++ )
            {
                int t=n-a*a-b*b-c*c;
                int d=sqrt(t);
                if(d*d==t)
                {
                    printf("%d %d %d %d\n",a,b,c,d);
                    return 0;
                }
            }
            
    return 0;
}

dfs枚举
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<math.h>

using namespace std;

int n; 
int st[5];

void dfs(int u)
{
    if(u>4)
    { 
		int sum=0;
        for(int i=1;i<=4;i++)
        {
			sum+=st[i]*st[i];
		
        }
        if(sum==n)
        {  
            for(int i=1;i<=4;i++)
            {
                cout<<st[i]<<" ";
            }
            puts("");
            exit(0);
        }
         return;
    }
    
    for(int i=0;i<=sqrt(n);i++)
    {
        st[u]=i;
        dfs(u+1);
        st[u]=-1;
    }
}

int main()
{
    cin>>n;
    dfs(1);
}

2.二分

【1】先枚举c,d,记录对应*c2+d2的值,用一个结构体,如下,重写<是为了按照字典序小的排列

struct Sum
{
int s,c,d;
bool operator<(const Sum &t ) const
{
if(s!=t.s) return s<t.s;
if(c!=t.c) return c<t.c;
return d<t.d;
}
}sum[N];

【2】再枚举a,b,计算t=n-a2+b2,二分查找/哈希表查找t 是否在第一步记录的数组中

二分做法
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<math.h>

using namespace std;

int n,m; 
const int N=2500010;
struct Sum
{
    int s,c,d;
    bool operator<(const Sum &t ) const
    {
        if(s!=t.s) return s<t.s;
        if(c!=t.c) return c<t.c;
        return d<t.d;
    }
}sum[N];

int main()
{
    cin>>n;
    //先枚举前两个
    for(int c=0;c*c<=n;c++)
        for(int d=c;c*c+d*d<=n; d++)
            sum[m++]={c*c+d*d,c,d};
            
    sort(sum,sum+m);
    
    for(int a=0; a*a<=n;a++)
        for(int b=0; a*a+b*b<=n; b++)
        {
            int t=n-a*a-b*b;
            int l=0,r=m-1;
            while(l<r)
            {
                int mid=l+r>>1;
                if(sum[mid].s>=t) r=mid;
                else l=mid+1;
            }
            
            if(sum[l].s==t)
            {
                printf("%d %d %d %d\n",a,b,sum[l].c,sum[l].d);
                return 0;
            }
        }
    return 0;
}

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<unordered_map>

#define x first
#define y second
using namespace std;

/****哈希表做法******/
typedef pair<int,int> PII;
const int N=2500010;
int n,m; 
unordered_map<int,PII> S;

int main()
{
    cin>>n;
    //先枚举前两个
    for(int c=0;c*c<=n;c++)
        for(int d=c;c*c+d*d<=n; d++)
        {
            int t=c*c+d*d;
            if(S.count(t)==0) S[t]={c,d};//只存第一个
        }
    
    for(int a=0; a*a<=n;a++)
        for(int b=0; a*a+b*b<=n; b++)
        {
            int t=n-a*a-b*b;
            if(S.count(t))//查找是否在其中
            {
                printf("%d %d %d %d\n",a,b,S[t].x,S[t].y);
                return 0;
            }
           
            
        }
    return 0;
}

1227. 分巧克力

  1. res+=(h[i]/mid)*(w[i]/mid);//长度为mid时可以分多少块巧克力
  2. 选取巧克力边长时具有二段性,若此时的边长不能满足,则大于它的都不能满足,知道用二分就简单了
#include<cstdio>
#include<iostream>

using namespace std;

const int N=100010;

int n,k;
int h[N],w[N];

bool check(int mid)
{
    int res=0;
    for(int i=0;i<n;i++)
    {
        res+=(h[i]/mid)*(w[i]/mid);//长度为mid时可以分多少块巧克力
        if(res>=k) return true;
    }
    return false;
}

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=0;i<n; i++)
    {
        scanf("%d %d",&h[i],&w[i]);
    }
    
    int l=1,r=1e5;
    while(l<r)
    {
        int mid=l+r+1 >>1;
        if(check(mid)) l=mid;
        else r=mid-1;
    }
    
    printf("%d\n",r);
    return 0;
}
1.2实数二分
while(l<r)
{
    int mid=(l+r)>>1;
    if(check(mid))	r=mid;
    else	l=mid+1;
} 
cout<<r<<endl;

790. 数的三次方根

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int main()
{
    double x;
    cin>>x;
    double l=-10000,r=10000;
    while(r-l>1e-8)
    {
        double mid= (l+r)/2;
        if(mid*mid*mid >=x) r=mid;
        else l=mid;
    }
    
    if((l*l*l)-x<1e-8)
         printf("%f\n",l);
    return 0;
}
2.前缀和

前缀和是一种优化的办法,一种思想的转变,事先记录静态区间内某一范围的和,能够快速求出子范围的和

1.一维前缀和 al+al+1+al+2+…ar=sr-sl-1; sr表示a0~ar的和;

2.二维前缀和 Sxy = S(x-1)y + Sx(y-1)-S(x-1)(y-1)+axy,计算(x1,y1),(x2,y2)子矩阵的和=Sx2y2-Sx2(y1-1)-S(x1-1)y2+S(x1-1)(y1-1);
Sxy表示(0,0)到(x,y)矩阵的和

99. 激光炸弹

1.想清楚准备怎么用RxR的矩阵遍历整个目标区域==》暴力枚举右下角的坐标(i,j),左上角为(i-R+1,j-R+1),代入公式计算子矩阵的和,遍历结果取最大值即可

#include<iostream>
#include<cstdio>

using namespace std;


int cnt,r;
const int N=5010;
int s[N][N];//充当原矩阵和前缀和矩阵


int main()
{
   cin>>cnt>>r;
   int n,m;//n为x最大值,m为y最大值
    n=m=r;
    r=min(5010,r);
    //读入所有目标,值存入地图
    for(int i=0;i<cnt;i++)
    {
        int x,y,w;
        cin>>x>>y>>w; 
        x++;y++;//坐标从1开始
        n=max(n,x),m=max(m,y);
        s[x][y]+=w;
    }
    
    //构建前缀和,s[i][j]代表格点上的值,但r边长矩形是在格子上移动
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
        }
    
    //枚举所有边长为r的矩阵,枚举右下角
    int res=0;
    for(int x=r;x<=n;x++)
        for(int y=r;y<=m;y++)
        {
            res=max(res,s[x][y]-s[x-r][y]-s[x][y-r]+s[x-r][y-r]);
        }
        
    cout<<res<<endl;
    
}

1230. K倍区间

法1.二重循环暴力枚举左右端点,循环中求(用一维前缀和)区间和,判断是否为k倍区间,是cnt++

法2.记录从0~r-1所有不同s[i]%k的余数值的个数,累加即为结果,因为(S[r]-S[l-1])%k==0时为一个k倍区间(思考)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

int n,k; 
typedef long long LL;
const int N=100010;
LL s[N],cnt[N];

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
    	scanf("%lld",&s[i]);
    	s[i]+=s[i-1];//构造前缀和
    }
  LL res=0;
  cnt[0]=1;
  for(int r=1;r<=n;r++)//从1~n记录每种余数的个数,相同余数的个数就是以r为右端点1~r中有多少个L也满足%k==该余数 
  {
  	res+=cnt[s[r]%k];
  	cnt[s[r]%k]++;
  }
  printf("%lld\n",res);
  return 0;
}

ACWing超棒哒

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值