之前 觉得二分挺好理解的,就没怎么花时间研究,直到上次校赛理解丘丘人那题,才知道二分原来不止于查找,果然蒟蒻就是蒟蒻
分巧克力
P8647 [蓝桥杯 2017 省 AB] 分巧克力 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这题的思路是对每一种可能的正方形边长进行讨论,观察数据(1≤Hi,Wi≤1e5),因此正方形可能的边长为1-1e5。对每一个边长,讨论能分成几个这样的正方形并用sum记录,如果sum大于等于人数k,即满足题意。
这里要理解一个长为h宽为w的长方形能分成(h/i)*(w/i)个边长为i的正方形。
暴力枚举
这里暴力枚举会WA两个点,为什么不是TLE,很困惑(在网上复制别人的暴力代码同样会WA那两个点)
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,k;
int h[N],w[N];
int sum;
int res;
signed main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&h[i],&w[i]);
}
for(int i=1;i<=1e5;i++)//巧克力所有可能的边长
{
sum=0;
for(int j=1;j<=n;j++)
{
sum+=(h[j]/i)*(w[j]/i);//可以分成几个正方形
}
if(sum>=k)
{
res=i;//保存边长
}else break;
}
cout<<res;
return 0;
}
二分AC代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,k;
int h[N],w[N];
int sum;
bool check(int x)
{
sum=0;
for(int i=1;i<=n;i++)
{
sum+=(h[i]/x)*(w[i]/x);
}
if(sum>=k) return true;
else return false;
}
signed main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&h[i],&w[i]);
}
//二分边长
int l=1,r=1e5,res=-1;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid))
{
res=mid;
l=mid+1;
}else r=mid-1;
}
cout<<res;
return 0;
}
求和
P8772 [蓝桥杯 2022 省 A] 求和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
有前缀和、差分,先简单了解一下这些算法
什么,差分是前缀和的升级版?那我们先了解一下前缀和
前缀和
前缀和就是从位置1到位置i这个区间内的所有的数字之和
前缀和s的输入
for (int i = 1; i <= n; i ++ )
{
cin >> a[i];
s[i] = s[i - 1] + a[i];
}
前缀和的优势:以(o1)的时间复杂度得到某块区间的总和
二维数组求前缀和
差分
C++算法——差分_c++调用差分函数_是挺秃然的齐齐哦的博客-CSDN博客
前缀和以及差分看这一篇就够了 - xbhog - 博客园 (cnblogs.com)
AC代码
这题知道了前缀和之后就很简单了(原来数组开小了也会RE啊
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n;
int a[N];
long long s[N];
long long sum;
signed main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
s[i]=s[i-1]+a[i];//求前缀和
}
for(int i=1;i<=n-1;i++)
{
sum+=a[i]*(s[n]-s[i]);
}
printf("%lld",sum);
return 0;
}
借教室
P1083 [NOIP2012 提高组] 借教室 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
复杂度O(mn)直接t了
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m;//天数和订单数量
long long restroom[N];//第i天可借教室
long long b[N];
int res;
signed main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&restroom[i]);
int num=m;
while(m--)
{
long long d;
int s,t;//租借数量,第几天开始和结束
scanf("%lld%d%d",&d,&s,&t);
memset(b,0,sizeof b);
b[s]+=-d,b[t+1]-=-d;
for(int i=1;i<=n;i++)//i是天数
{
b[i]=b[i-1]+b[i];
restroom[i]+=b[i];
if(restroom[i]<0)
{
cout<<-1<<endl;
res=i;
cout<<num-m;
return 0;
}
}
}
cout<<res;
return 0;
}
AC代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 10;
int n, m, ans;
//二分能满足多少订单(logm),每次差分+前缀和O(n)后扫一遍所有天数O(n),判断能否满足;
long long r[MAXN], cha[MAXN];
struct Order{
long long d;
int s, t;
}a[MAXN];
//检查能否满足1...num这些订单,如果能,返回true;如果不能,返回false;
bool check(int num)
{
//差分.当然也可以用两个数组:一个差分数组,一个原数组;
//做完操作后将差分数组做一遍前缀和再与原数组相加即可。
for(int i = 1; i <= num; ++i){
cha[ a[i].s ] += a[i].d;
cha[ a[i].t+1 ] -= a[i].d;
}
for(int i = 1; i <= n; ++i){
cha[i] += cha[i-1];
if(cha[i] > r[i])return false;
}
return true;
}
int main()
{
cin>>n>>m;
for(int i = 1; i <= n; ++i)scanf("%d", &r[i]);
for(int i = 1; i <= m; ++i)scanf("%d%d%d", &a[i].d, &a[i].s, &a[i].t);
int lef = 1, rig = m;
while(lef <= rig)
{
int mid = (lef + rig)>>1;
if(check(mid)){
ans = mid; //如果能满足,就记录一下当前订单数
lef = mid + 1;
}
else rig = mid - 1;
for(int i = 1; i <= n; ++i)cha[i] = 0;
}
if(ans == m)cout<<0<<endl;
else{
cout<<-1<<endl;
//因为当恰好不满足的时候二分退出,而ans记录的是满足的(最后一个)一个订单,
//所以ans+1就是恰好不满足的那一个订单。
cout<<ans+1<<endl;
}
return 0;
}