二分和前缀和
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;
}
最坏的情况就是柱子排列是降序的,一直在减少能量,算出最多需要的能量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;
}
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;
}
- res+=(h[i]/mid)*(w[i]/mid);//长度为mid时可以分多少块巧克力
- 选取巧克力边长时具有二段性,若此时的边长不能满足,则大于它的都不能满足,知道用二分就简单了
#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;
#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)矩阵的和
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;
}
法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;
}