1 前缀和
1 k倍区间)
https://www.luogu.com.cn/problem/P8649
之差是k的倍数,性质:两个数取余k相同时,之差是k的倍数。所以只需要记录每个前缀和取模k,每新增一个曾经出现的余数,它能和之前存入的m个相同余数组成k差,ans+=m。
并且要注意0的位置。这相当于有的不用减第一个数就是k倍,关键在于想到i,j区间都可以加上前面的部分,这样好维护
2 抓娃娃)
https://www.luogu.com.cn/problem/P9426
关键在于题目有一个隐含条件,最小区间大于最大线段。所以有一个转化:包含中点就一定包含线段。现在就是记录中点,然后前缀和求每个区间中点个数。
开小了1e5+10会全部re,要注意数据范围,还需要longlong
后面的也需要维护,不是只维护到maxx,int范围2e9,数组5e8,longlong 9e18,数组2e8
点击查看代码
#include<bits/stdc++.h>
#define int long long
const int maxn=2e6+10;
using namespace std;
int l,r;
int n,m,s[maxn],a[maxn],b[maxn],ans=0;
signed main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>l>>r;
a[l+r]++;
}
for(int i=1;i<=maxn;i++)
{
a[i]+=a[i-1];
}
for(int i=1;i<=m;i++)
{
cin>>l>>r;
l*=2,r*=2;
cout<<a[r]-a[l-1]<<endl;
}
return 0;
}
3 挖矿)
https://www.luogu.com.cn/problem/P10904
更新j出错
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 2e6 + 10;
int n, m, ans;
int a[maxn],b[maxn];
signed main()
{
cin >> n >> m;
for (int i = 1;i <= n;i++)
{
int x;
cin >> x;
if (x < 0)
b[-x]++;
else
a[x]++;
}
for (int i = 1;i < maxn;i++)
{
a[i] += a[i - 1];
b[i] += b[i - 1];
}
for (int i = 0;i <= m;i++)
{
//枚举左端点
int t = a[i];
if (m - 2 * i > 0)
t += b[m - 2 * i];
//j = 0;
//while (2 * j + i <m)
//{
// //例如,假设i是0,m是5,那么2j+0 <=5,最大的j是2(因为2*2=4<=5,而j=3的话2*3=6>5),所以这样更新不对
// j++;
//
//}
ans = max(ans,t);
}
for (int i = 0;i <= m;i++)
{
int t = b[i];
if (m - 2 * i > 0)
t += a[m - 2 * i];
ans = max(ans, t);
}
cout << ans << endl;
}
点击查看代码
#include <bits/stdc++.h>
using namespace std;
// 定义数组的最大长度
const int N = 2e6 + 10;
// n 表示矿洞数量,m 表示最大移动距离
// l 数组用于记录负坐标矿洞的数量,r 数组用于记录正坐标矿洞的数量
// ans 用于记录最大矿石数量,cnt 用于记录原点矿洞的数量
int n, m, l[N], r[N], ans, cnt;
int main() {
// 读取矿洞数量 n 和最大移动距离 m
scanf("%d%d", &n, &m);
// 遍历每个矿洞
for (int i = 1, x; i <= n; i++) {
// 读取当前矿洞的坐标
scanf("%d", &x);
if (x < 0) {
// 如果坐标为负,将对应负坐标的矿洞数量加 1
l[-x]++;
} else if (x > 0) {
// 如果坐标为正,将对应正坐标的矿洞数量加 1
r[x]++;
} else {
// 如果坐标为 0,原点矿洞数量加 1
cnt++;
}
}
// 计算负坐标矿洞数量的前缀和
for (int i = 1; i <= m; i++) {
l[i] += l[i - 1];
// 计算正坐标矿洞数量的前缀和
r[i] += r[i - 1];
}
// 枚举所有可能的移动方案
for (int i = 1, t; i <= m; i++) {
// 方案一:先向左移动 i 个单位,再向右移动
t = l[i];
if (m - i * 2 > 0) {
// 如果剩余移动距离大于 0,加上向右能挖到的矿洞数量
t += r[m - i * 2];
}
// 更新最大矿石数量
ans = max(ans, t);
// 方案二:先向右移动 i 个单位,再向左移动
t = r[i];
if (m - i * 2 > 0) {
// 如果剩余移动距离大于 0,加上向左能挖到的矿洞数量
t += l[m - i * 2];
}
// 更新最大矿石数量
ans = max(ans, t);
}
// 输出最大矿石数量加上原点矿洞数量
printf("%d\n", ans + cnt);
return 0;
}
2 二分
!!!!有时需要有效性检查,因为可能所有元素都大于小于,而不满足小于等于大于等于
向左找
int bin1(int l,int r)
{
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
return l;
}
向右找
int bin2(int l,int r)
{
while(l<r)
{
int mid=(l+r+1)>>1;
if(check(mid))
l=mid;
else
r=mid-1;
}
return l;
}
1 立定跳远)
https://www.luogu.com.cn/problem/P10909
注意两个整数直接相除会直接取整,所以需要先变为浮点数。再进行向上取整ceil或向下取整floor。并且使用全局变量记得有时需要重置为0;
点击查看代码
#include<bits/stdc++.h>
#define int long long
const int maxn = 2e6 + 10;
using namespace std;
int l, r;
int maxx = -1;
int n, m, a[maxn], b[maxn], ans = 0;
int check(int s)
{
int flag = 0;
//忘记重置全局变量
int cou = 0;
for (int i = 1; i <= n; i++)
{
int c = a[i] - a[i - 1];
if (c > s)
{
if (c <= 2 * s && flag == 0)
flag = 1;
else
{
cou += ceil(c *1.0/ s) - 1;
//当两个int整数相除时,直接舍去小数部分
//这里非常容易出错,忘写ceil,并且cou只加了1
//两个整数直接相除会取整,那向上取整就不管用了
if (flag == 0)
{
flag = 1;
//忘记改变
cou--;
}
}
if (cou > m)
return 0;
}
}
return 1;
}
int bin1(int l, int r)
{
while (l < r)
{
int mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
signed main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
maxx = max(maxx, a[i]);
}
cout << bin1(1, maxx);
}
2 杨辉三角形)
https://www.luogu.com.cn/problem/P8749
为了保证二分的单调性并且结合杨辉三角性质,看为第i行第j个对角线。那这个数就是C(i,j),找到i,j就可以确定这是第几个数
点击查看代码
#include<cstdio>
typedef long long LL;
const LL INF=1e9;
LL n;
LL C(LL a,LL b){
LL res=1;
for(LL i=a,j=1;j<=b;i--,j++){
res=res*i/j;
if(res>n) // fixed
return res;
}
return res;
}
int main(){
scanf("%lld",&n);
// 只需遍历 16 行
if(n==1){
printf("1");
return 0;
}
for(int i=16;i>=0;i--){
LL l=2*i,r=INF,mid,lim;
while(l<=r){
mid=(l+r)>>1,lim=C(mid,i);
//第mid行,第i条对角线
if(lim==n){
printf("%lld",(mid+1)*mid/2+i+1);
//因为从0行开始,但是因为在计算元素位置时,实际索引从 1 开始
return 0;
}else if(lim<n)
l=mid+1;
else{
r=mid-1;
}
}
}
return 0;
}
待修改
#include<bits/stdc++.h>
//#define int long long
const int maxn = 2e6 + 10;
using namespace std;
int l, r;
int i, j;
int maxx = -1;
int n, m, a[1000][1000], b[maxn], ans = 0;
int check(int s)
{
}
int bin1(int l, int r)
{
while (l < r)
{
int mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
signed main()
{
cin >> n;
a[1] = a[2] = 1;
int x = 1;
for (i = 1;i < 1000;i++)
{
for (j = 1;j <=i;j++)
{
if (i == 1 || i == j)
{
a[i][j] = 1;
}
else {
a[i][j] = a[i - 1][j] + a[i - 1][j - 1];
}
if(j<=ceil(i/2.0))
b[x++] = a[i][j];
}
}
if (n == 1)
cout << 1 << endl;
else
int ans= bin1(1, 1e9+10);
}
3 递增三元组)
https://www.luogu.com.cn/problem/P8667
lower_bound 不小于 upper_bound大于
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1e5 + 10;
int n, m, ans,maxx,flag;
int a[maxn],b[maxn],c[maxn];
/*
int bin(int l, int r)
{
while (l < r)
{
int mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
*/
signed main()
{
cin >> n;
for (int i = 1;i <= n;i++)cin >> a[i];
for (int i = 1;i <= n;i++)cin >> b[i];
for (int i = 1;i <= n;i++)cin >> c[i];
sort(a + 1, a + 1 + n);
sort(b + 1, b + 1 + n);
sort(c + 1, c + 1 + n);
//排序,好进行二分
for(int i = 1;i <= n;i++)
{
int ans1 = lower_bound(a + 1, a + 1 + n, b[i]) - (a + 1)-1+1;
int ans2 = c+n+1-upper_bound(c + 1, c + 1 + n, b[i]);
//二分找出i的种类数和j的种类数
//upper大于,lower大于等于
//c+n+1其实是最后一个元素的下一个位置,所以ans2不需要再+1
ans += ans1 * ans2;
}
cout << ans << endl;
}
正常二分写,一直不对
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1e5 + 10;
int n, m, ans,maxx,flag;
int a[maxn],b[maxn],c[maxn];
int bin1(int l, int r,int x)
{
while (l < r)
{
int mid = (l + r) / 2;
if (c[mid]>=x) r = mid;
else l = mid + 1;
}
return l;
}
int bin2(int l, int r,int x)
{
while (l < r)
{
int mid = (l + r+1) / 2;
if (c[mid]<=x) l= mid;
else r=mid-1;
}
return l;
}
signed main()
{
cin >> n;
for (int i = 1;i <= n;i++)cin >> a[i];
for (int i = 1;i <= n;i++)cin >> b[i];
for (int i = 1;i <= n;i++)cin >> c[i];
sort(a + 1, a + 1 + n);
sort(b + 1, b + 1 + n);
sort(c + 1, c + 1 + n);
for (int i = 1;i <= n;i++)
{
int ans1 = bin1(1, n, b[i]);
int ans2=n-bin2(1, n, b[i])+1;
//为啥这里要+1呢:
ans += ans1 * ans2;
}
cout << ans << endl;
}
正常二分 改不对
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1e5 + 10;
int n, m, ans,maxx,flag;
int a[maxn],b[maxn],c[maxn];
int bin1(int l, int r,int x)
{
while (l < r)
{
int mid = (l + r) / 2;
if (a[mid]>=x) r = mid;
else l = mid + 1;
}
return l;
}
int bin2(int l, int r,int x)
{
while (l < r)
{
int mid = (l + r+1) / 2;
if (c[mid]<=x) l= mid;
else r=mid-1;
}
return l;
}
signed main()
{
cin >> n;
for (int i = 1;i <= n;i++)cin >> a[i];
for (int i = 1;i <= n;i++)cin >> b[i];
for (int i = 1;i <= n;i++)cin >> c[i];
sort(a + 1, a + 1 + n);
sort(b + 1, b + 1 + n);
sort(c + 1, c + 1 + n);
for (int i = 1;i <= n;i++)
{
int ans1 = bin1(1, n, b[i])-1;
int ans2=n-bin2(1, n, b[i]);
ans += ans1 * ans2;
}
cout << ans << endl;
}
桶排序,记录每个数出现的个数,sum[i]表示小于等于i的个数
桶排序
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1e5+10;
int sum1[maxn],sum2[maxn];
int a[maxn],b[maxn],c[maxn];
signed main(){
int n;
scanf("%lld",&n);
for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
for (int i=1;i<=n;i++) scanf("%lld",&b[i]);
for (int i=1;i<=n;i++) scanf("%lld",&c[i]);
for (int i=1;i<=n;i++){
sum1[a[i]]++;
sum2[c[i]]++;
}
for (int i=1;i<=(int)1e5;i++){
sum1[i]+=sum1[i-1];
}
for (int i=(int)1e5;i>=0;i--){
sum2[i]+=sum2[i+1];
}
int ans=0;
for (int i=1;i<=n;i++){
ans+=sum1[b[i]-1]*sum2[b[i]+1];
}
printf("%lld",ans);
return 0;
}
4)青蛙跳石头
错误代码:1:忽略了要上k天课,所以要检查k次,并且因为是逐渐递减的,不能直接a[i]-=k.2:忽略了备份代码,a数组在第一个测试点改变,所以我们要存起来a。
正解:往返和x次课看成2x只青蛙从起点开始跳跃,如果y满足,那么任意一个长度为y的区间,石头高度和必须大于2x
我的错误代码
#include<bits/stdc++.h>
#define int long long
int n, x;
const int maxn = 1e5 + 10;
int a[maxn], b[maxn];
int maxx = 1e4 + 10;
using namespace std;
int check(int y)
{
//每次都从最远的开始看
int now = 0;
for (int i = n;i >= 0;i--)
{
if (a[i] > 0)
{
if ((i - now) <= y)
{
a[i]--;
now = i;
if (now == n) break;
i = n+1;
}
/*else return false;*/
}
if (i == now) return 0;
}
if (now != n) return 0;
for (int i = 0;i <= n;i++)
{
if (a[i] > 0)
{
if ((i - now) <= y)
{
a[i]--;
now = i;
if (now == 0) break;
i = -1;
}
}
if (i == now) return 0;
}
if (now != 0) return 0;
return true;
}
int bin1(int l, int r)
{
while (l < r)
{int mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return r;
}
signed main()
{
cin >> n >> x;
a[0] = a[n] = maxx;
for (int i = 1;i < n;i++)cin >> a[i];
/*cout << "check"<<check(4);*/
cout << bin1(1, maxn) << endl;
return 0;
}
修改代码tle
#include <bits/stdc++.h>
#define int long long
int n, x;
const int maxn = 1e5 + 10;
int a[maxn], b[maxn];
using namespace std;
// 检查跳跃能力 y 是否满足要求
bool check(int y) {
// 备份 a 数组
for (int i = 0; i <= n; i++) {
b[i] = a[i];
}
int now = 0;
// 去学校的过程
for (int k = 0; k < x; k++) {
now = 0;
while (now < n) {
int next = -1;
// 尽可能远地寻找能跳到的石头
for (int i = n; i > now; i--) {
if (b[i] > 0 && (i - now) <= y) {
next = i;
break;
}
}
if (next == -1) {
// 找不到能跳到的石头,返回 false
return false;
}
b[next]--;
now = next;
}
}
// 回家的过程
for (int k = 0; k < x; k++) {
now = n;
while (now > 0) {
int next = -1;
// 尽可能远地寻找能跳到的石头
for (int i = 0; i < now; i++) {
if (b[i] > 0 && (now - i) <= y) {
next = i;
break;
}
}
if (next == -1) {
// 找不到能跳到的石头,返回 false
return false;
}
b[next]--;
now = next;
}
}
return true;
}
// 二分查找最小跳跃能力
int bin1(int l, int r) {
while (l < r) {
int mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return r;
}
signed main() {
cin >> n >> x;
// 初始化起点和终点的石头高度
a[0] = a[n] = x;
for (int i = 1; i < n; i++) {
cin >> a[i];
}
cout << bin1(1, n) << endl;
return 0;
}
正解
#include <bits/stdc++.h>
#define i64 long long
using namespace std;
const int N = 1e5 + 5;
int n, x;
i64 arr[N], sum[N];
// 检查跳跃能力 y 是否满足要求
bool check(int y) {
for (int i = y; i <= n - 1; i++) {
if (sum[i] - sum[i - y] < 2 * x) {
return false;
}
}
return true;
}
/*
事实证明,从y开始枚举更好算
int check(int m)
{
for (int i = 1;i <= n-m;i++)
{
if (a[i + m-1] - a[i-1] < 2 * x)return 0;
}
return 1;
}
*/
int main() {
//因为没对起点和终点检查,所以不用初始化
cin >> n >> x;
for (int i = 1; i <= n - 1; i++) {
cin >> arr[i];
sum[i] = sum[i - 1] + arr[i];
}
int l = 1, r = n;
while (l < r) {
int mid = (l + r) / 2;
if (check(mid)) {
r = mid;
}
else {
l = mid + 1;
}
}
cout << r << endl;
return 0;
}
5 冶炼金属
一直在错,因为ceil是向上取整,floor是向下取整。而且二分真假的返回条件不是!=或者=。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 10;
double a[maxn], b[maxn];
int n;
int minn, maxx;
int check1(int x)
{
for (int i = 1;i <= n;i++)
{
if (floor(a[i] / x) > b[i])
return 0;
}
return 1;
}
int check2(int x)
{
for (int i = 1;i <= n;i++)
{
if (floor(a[i] / x) < b[i])
return 0;
}
return 1;
}
int main() {
cin >> n;
for (int i = 1;i <= n;i++)
{
int x, y;
cin >> a[i] >> b[i];
}
int l = 1, r = 1e4 + 10;
while (l < r)
{
int mid = (l + r) / 2;
if (check1(mid) == 1)
r = mid;
else
l = mid + 1;
}
minn = l;
l = 1, r = 1e4 + 10;
while (l < r)
{
int mid = (l + r + 1) / 2;
if (check2(mid) == 1)
l = mid;
else
r = mid - 1;
}
maxx = l;
cout << minn << " " << maxx << endl;
}
浙公网安备 33010602011771号