《算法竞赛进阶指南》学习笔记 0x00基本算法

0x00 基本算法

0.1 位运算
运算符号
and,&
or,|
not,~
异或xor,^

tips:在m位二进制数中,方便起见,通常称最低位为第0位,在高位为m-1位,依次类推。

0.1.1 补码

32位无符号整数(unsigned int):直接把32位编码C作为32位二进制数N
32位有符号整数(int):以最高位为符号位
补码:-x = ~x+1 (反码+1)

32位补码表示unsigned intint
000000……00000000
011111……11111121474836472147483647
100000……0000002147483648-2147483648
111111……11111114294967295-1

tips:0x3F3F3F3F常用于将数值初始化为正无穷

  1. 0x3F3F3F3F * 2 < 0x7FFFFFFF ,即加上一个小于它的数不会导致溢出,在图论中可以避免加法算术上溢和繁琐的判断
  2. 整数的每8位字节相同 (memset(a,val,sizeof(a))只能赋值每8位相同的int )
0.1.2 移位运算

左移:在二进制表示下把数字同时向左移动,低位以0填充,高位越界后舍弃。

算术右移:在二进制补码表示下数字向右移动,高位以符号位填充,低位越界后舍弃
n>>1 = [n/2.0](向下取整)

0.1.3 二进制状态压缩(待补充)
0.1.4 成对变换

对于非负整数 n,
当n为整数时:n xor 1 等于 n+1
当n为奇数时:n xor 1 等于 n-1
因此“0与1”,“2与3”,“4与5”……关于 xor1运算构成成对变换,即可用异或来实现配偶
这一性质经常用于图论邻接表中边集的存储。

0.1.5 lowbit 运算

lowbit运算:非负整数 n 在二进制表示下最低位的 1 及其后边所有的 0 构成的数值。

公式:lowbit(n)=n&(~n+1)=n&(-n) -n:n的补码

0.2 递推和递归
0.2.1 递推与递归的宏观描述

递推:以已知的问题边界为起点向原问题正向推导的扩展方式

递归:以原问题为起点尝试寻找把状态空间缩小到已知的问题边界的路线

递归操作的关键点

自身调用自身:求解原问题规模缩小以后的子问题

回溯是还原现场:求解子问题失败,程序重新回到当前问题寻找其他的变换路线

例题:POJ1598 四座汉诺塔

#include<iostream>
#include<cstring>
using namespace std;

int d[15];
int f[15];

int main(){
    d[1] = 1;
    for (int i = 2; i <= 12; i++){
        d[i] = d[i - 1] * 2 + 1;
    }
    memset(f, 0x3f3f3f, sizeof(f));
    f[0] = 0;
    for (int i = 1; i <= 12; i++){
        for (int j = 0; j < i; j++){
            f[i] = min(f[i], f[j] * 2 + d[i - j]);
        }
    }
    for (int i = 1; i <= 12; i++)
        cout << f[i] << endl;

}
0.2.2 分治

分治:把一个问题划分为若干个规模更小的同类子问题,对这些子问题递归求解,然后在回溯时通过它们推导出原问题的解。

例题 :POJ1845

#include<iostream>
#include<cstring>
using namespace std;
const int mod = 9901;

int ksm(int a,int b){
    if(!a)  return 0;
    a %= mod;
    int sum = 1;
    while(b){
        if( b&1 )   sum = (sum*a) % mod;
        b = b >> 1;
        a = (a * a) % mod;
    }
    return sum;
}

int sum(int p, int c){
    if( c == 0)    return 1;
    if( c%2 == 0 )
        return ((1 + ksm(p, c / 2))*sum(p, c/2-1)+ksm(p,c)) %mod;
    else
        return ((1+ksm(p,(c+1)/2))*sum(p,(c-1)/2)) %mod;
}

int main(){
    int a, b;
    /*
    while(cin >> a >> b){
        cout << sum(a, b) << endl;
    }*/
    cin >> a >> b;
    int res = 1;
    for (int p = 2; p <= a; p++){
        int c = 0;
        while( a%p==0 ){
            a /= p;
            cpp;
        }
        if(c) res = (res*sum(p, c*b)) %mod;
    }
    if( !a )  res = 0;
    cout << res << endl;

}

例题:POJ3714 平面最近点对

题意:给出a阵营n个点和b阵营n个点,求两个阵营最短距离

解法:分治+归并排序

#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#include<cstdio>
using namespace std;
const int maxn = 200010;
const int INF = 1e9;

struct Point{
    double x, y;
    bool type;

    bool operator< (const Point &W)const{
        return x < W.x;
    }
};

Point points[maxn], temp[maxn];

double dist(Point a,Point b){
    if( a.type == b.type)
        return INF;
    double dx = a.x - b.x, dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}

double dfs(int l,int r){
    if( l>=r )
        return INF;

    int mid = (l + r)>>1;
    double mid_x = points[mid].x;
    double res = min(dfs(l, mid), dfs(mid + 1, r));

    //归并排序
    {
        int k = 0, i = l, j = mid + 1;
        while( i<=mid && j<=r)
            if( points[i].y < points[j].y )
                temp[k++] = points[i++];
            else
                temp[k++] = points[j++];

        while( i<=mid )
            temp[k++] = points[i++];
        while( j<=r )
            temp[k++] = points[j++];

        for (i = 0, j = l; i<k; i++, j++)
            points[j] = temp[i];
    }

    int k = 0;
    for (int i = l; i <= r; i++){
        if( points[i].x >= mid_x-res && points[i].x <=mid_x+res)
            temp[k++] = points[i];
    }
    for (int i = 0; i < k; i++)
        for (int j = i - 1; j >= 0 && temp[i].y - temp[j].y < res; j--)
            res = min(res, dist(temp[i], temp[j]));
    return res;
}

int main()
{
    int T, n;
    cin >> T;

    while (T -- )
    {
        scanf("%d", &n);
        for (int i = 0; i < n; i ++ )
        {
            scanf("%lf%lf", &points[i].x, &points[i].y);
            points[i].type = 0;
        }
        for (int i = n; i < 2 * n; i ++ )
        {
            scanf("%lf%lf", &points[i].x, &points[i].y);
            points[i].type = 1;
        }

        sort(points, points + n * 2);

        printf("%.3lf\n", dfs(0, n * 2 - 1));
    }

    return 0;
}


0.2.3 分形

确定分图形坐标+dfs

#include<iostream>
#include<string>
#include<vector>
#include<cmath>
using namespace std;
const int maxn = 10100;
int  mp[maxn][maxn];

void sol(int x,int y,int cur){
    if( cur==1 )
        mp[x][y] = 1;
    else{
        int d = pow(3, cur - 2);
        sol(x, y, cur - 1);
        sol(x + 2 * d, y, cur - 1);
        sol(x + d, y + d, cur - 1);
        sol(x, y + 2 * d, cur - 1);
        sol(x + 2 * d, y + 2 * d, cur - 1);
    }    
}

int main(){
    ios::sync_with_stdio(false);
    int n;
    cin >> n;
    sol(0, 0, 7);
    while( n != -1 ){
        int len = pow(3, n - 1);
        for (int i = 0; i < len; i++){
            for (int j = 0; j < len; j++){
                if( mp[i][j] )
                    cout << "X";
                else
                    cout << " ";
            }
            cout << endl;
        }
        cout << "-" << endl;
        cin >> n;
    }
}
0.2.4 递归的非递归表示

使用模拟的方法,使用一个数组模拟栈。

#include<cstdio>
#include<iostream>
#include<stack>
using namespace std;

int n, m;

struct State{
    int pos;
    int u;//遍历位置
    int sum;//已选择的数
    int state;//遍历数的状态
};


//这一段函数未被调用,只作为说明pos对应的程序段
void dfs(int u,int sum,int state){
    //0:
    if(sum + n - u < m) return;
    if(sum == m){
        for (int i = 0; i < n; i++)
            if(state >> i&1 )
                cout << i + 1 << ' ';
        cout << endl;
        return;
    }

    dfs(u + 1, sum + 1, state | 1 << u);

    //1:
    dfs(u + 1, sum, state);

    //2
}


int main(){
    stack<State> stk;
    stk.push({0,0,0,0,});
    cin >> n >> m;
    while( stk.size() ){
        State t = stk.top();
        stk.pop();
        switch (t.pos){
            case 0:
                if(t.sum + n - t.u < m )
                    continue;
                if( t.sum == m ){
                    for (int i = 0; i < n; i++)
                        if(t.state >> i&1 )
                            cout << i + 1 << ' ';
                    cout << endl;
                    continue;
                }
                t.pos = 1;
                stk.push(t);
                stk.push({0,t.u + 1, t.sum + 1, t.state | 1 << t.u});
                break;
            case 1:
                stk.push({0,t.u + 1, t.sum, t.state});               
                break;
            case 2:
                stk.pop();
        }
    }

}
0.3 前缀和与差分
0.3.1 前缀和

对于一个给定的数列A,它的前缀和数列S

S[i]=A[1]+A[2]+A[3]+…+A[4]

一个部分和,即数列下标区间内的数的和,可表示为前缀和相减的形式

sum(l,r) = sum[r] - sum[l-1]

0.3.2 二维前缀和

设目标矩形的四个点坐标为(x,y)(x,j)(i,y)(i,j)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JAI2SG1v-1616723631857)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1596532771982.png)]

S=sum[i] [j]-sum[i] [y]-sum[x] [j]+sum[x] [y]

例题:BZOJ218

#include<iostream>
using namespace std;

const int maxn = 5010;

int g[maxn][maxn];

int main(){
    int N, R;
    cin >> N >> R;
    int n = R, m = R;
    for(int i = 0; i < N; i++){
        int x, y, w;
        cin >> x >> y >> w;
        x++, y++;//起点坐标从1开始
        n = max(n, x), m = max(m, y);
        g[x][y] += w;
    }

    //构建前缀和
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            g[i][j] += g[i - 1][j] + g[i][j - 1] - g[i-1][j-1]; //原先每个点已赋值

    //遍历区域右下角的点
    int res = 0;
    for (int i = R; i <= n; i++)
        for (int j = R; j <= m; j++)
            res = max(res, g[i][j]-g[i-R][j] - g[i][j-R] + g[i-R][j-R]);

    cout << res << endl;
}

0.3.3 差分

对于一个给定数列A,它的差分数列B定义为:
B[1] = A[1],B[i] = A[i] - A[i-1] (2 <= i <= n)

由此可见,前缀和与差分是一组互逆运算

利用差分可以把原序列上的 区间操作 转化为差分序列上的 单点操作

例题:CH0304
给定一个序列,每次可选择一个区间+1或-1,求至少需要多少次操作才能使数列中所有数一样,并求出最少次数的方案数

我们可以把区间操作转换为差分序列b[n]的操作,当b2……bn全为0时满足题目条件。当对区间 [i,j] 进行操作时,相当于选择了一对 bi bj+1一个+1,一个-1.

设b[n]正数和为p,负数和为q,那么可执行 min(p,q)次使正负和抵消一部分,剩下正数或负数的差分和 |p-q|
然后可将剩下的值选择与 b1 (产生|p-q|种序列)或 bn+1(产生1种序列) 配对,即 |p-q|+1种方案数

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
const int maxn = 100010;

int a[maxn];//原序列
int b[maxn];//差分

int main(){
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = n; i > 1; i--)
        b[i] = a[i] - a[i - 1];
    b[1] = a[1];
    //统计差分
    LL pos = 0;//差分正值和
    LL neg = 0;//差分负值和
    for (int i = 2; i<=n; i++)
        if(b[i]>0)
            pos += b[i];
        else
            neg -= b[i];

    cout << min(pos, neg) + abs(pos - neg) << endl;
    cout << abs(pos - neg) + 1 << endl;
} 
0.4 二分
0.4.1 整数集合上的二分

对于闭区间[l,r],整数集合上的二分有两种情况

①在单调递增序列中查找 ≥x 的数中最小的一个(x或x的后继)
mid = (l+r)/2 区间划分为 [l,mid] 和 [mid+1,r]

while (l<r){
	int mid = (l+r) >> 1;
    if( a[mid] >=x ) r = mid;
	else l = mid + 1;
}
return a[l];

②在单调递增序列中查找 ≤x 的数中最小的一个(x或x的前驱),
mid = (l+r+1)/2 区间划分为 [l,mid-1] 和 [mid,r]

while(l<r){
    int mid =(l+r+1) >> 1;
    if( a[mid] <= x) l=mid;
    else r = mid-1;
}
return a[l];

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CkiMXalA-1616723631869)(D:\ipanda\ACM\library\pictures\IMG_20200815_102358.jpg)]

Tips:

  1. 右移运算是向下取整,而整数除法是向零取整,在二分值域包含负数时整数除法不能正常工作

  2. (l+r)>>1 不会取到 r 这个值 ,(l+r+1)>>1 不会取到 l 这个值 可以利用这一性质处理无解的情况:
    把最初的二分区间 [1,n] 分别扩大为 [1,n+1] 和 [0,n]

0.4.2 实数域上的二分

实数域上二分与整数域不同的是需要确定好精度 eps,以 l+eps < r 为循环条件,每次根据在mid上的判定选择
r = mid 或 l = mid 分支之一即可。

一般需要保留 k 位小数时,取 eps = 10 -(k+2)

while( l+eps < r ){
	double mid = (l+r)/2;
	if( check(mid) ) r = mid;
	else l = mid; 
}

也可以采用循环固定次数的二分方法

for(int i=0; i<100; i++){
	doubel mid = (l+r)/2;
	if( check(mid) ) r=mid ;
	else l =mid;
}
0.4.3 三分法求单峰值函数的极值

以单峰函数 f 为例,我们在函数定义域 [l,r] 上任取两个点 lmid 和 rmid ,把函数分为三段

  1. 若 f(lmid) < f(rmid), 令 l=lmid

  2. 若 f(lmid) > f(rmid), 令 r=rmid

while( l+eps < r ){
    double lmid = l+(r-l)/3;
    double rmid = r-(r-l)/3;
    if( f(1mid) < f(rmid)) l = lmid;
    else r = rmid;			
}

如果函数不严格单调,即在函数中存在一段值相等的部分,那么无法判断定义域的左右边界如何缩小,三分法不再适用

0.4.4 二分答案转化为判定

借助二分,我们把求最优解的问题转化为给定一个 mid ,判定是否存在一个可行方案评分达到 mid 的问题。


//把n书分成m组,每组厚度之和<=size,是否可行
bool valid(int size){
    int group = 1, rest = size;
    for (int i = 1; i <= n;i++){
        if(rest >=a[i] )
            rest -= a[i];
        else
            group++, rest = size - a[i];
    }
}

int main(){
    //二分答案,判定每组厚度之和不超过二分的值时能否在m组内把书分完
    int l = 0, r = sum_of_ai;
    while (l<r){
        int mid = (l + r) / 2;
        if( vaild(mid) )
            r = mid;
        else
            l = mid + 1;
    }
    cout << l << endl;
}

例题:POJ2018

#include<cstdio>
#include<iostream>
using namespace std;
const int maxn = 100010;
int n, m;
int a[maxn];
double sum[maxn];

bool check(double avg){
    for (int i = 1; i <= n; i++)
        sum[i] = sum[i - 1] + a[i] - avg;
        //将前缀和每一项减去平均数,转换判定为是否存在一个子段非负
    double minv=1e10;
    for (int i = m; i <= n; i++){
        minv = min(minv, sum[i - m]);
        if( sum[i]>minv)
            return true;
    }
        return false;
}

int main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i++){
        cin >> a[i];
    }
    double l = 1;
    double r = 2000;
    while( l+1e-5 < r){
        double mid = (l + r) / 2;
        if( check(mid) )    l = mid;
        else    r = mid;
    }
    printf("%d", (int)(1000 * r));
}
0.5 排序
0.5.1 离散化
void discrete(){
    sort(a+1,a+n+1);
    for(int i=1; i<=n; i++)
        if(i==1 || a[i]!=a[i-1])
            b[++m] =a[i];
}

int query(int x){
    return lower_bound(b+1,b+m+1, x) - b;
}
0.5.2 中位数

模型1:货仓选址

在X轴上有N个商店,其位置位xi(1<i<N),现需要求将货仓在X轴上某一 点,求货仓建在何处时使得货仓到各商店距离之和最小。

货仓应建在中位数处,即把A排序后,当N为奇数,货仓建在A[(N+1)/2],当N为偶数时建在A[N/2] ~ A[N/2+1]之间的任何位置。

模型2:均分纸牌

有N堆纸牌,每堆上有若干张,但纸牌总数必为N的倍数。可以在任一堆上取一张张纸牌,然后移动到相邻的一堆中,求至少要多少步操作才能让每个人受中共持有的纸牌数相等。

因为第一堆纸牌只能与第二堆进行操作,所以我们先考虑第一堆从第二堆拿来或拿去|C[1]-T/N |,T为纸牌的总数。以此方式考虑第二堆、第三堆,在这过程中即使某个C[i]变成负数也没关系,因为最终结果每堆都会变成 T/M张。

我们设 A[i] =C[i] -T/M,那么答案就是A[i]的前缀和之和的绝对值。sum|S[i]|

#include<iostream>
using namespace std;
const int maxn = 1000010;
typedef long long LL;
int a[maxn];
int s[maxn];

int main(){
    ios::sync_with_stdio(false);
    int n;
    int ans=0;
    LL t = 0;
    cin >> n;
    for (int i = 1; i <= n; i++){
        cin >> a[i];
        t += a[i];
    }
    t /= n;
    for (int i = 1; i <= n; i++){
        a[i] -= t;
        s[i] = s[i - 1] + a[i];
        ans += s[i];
    }
    if( ans<0){
        ans = -ans;
    }
    cout << ans << endl;
    
}

模型3:糖果传递(环形均分纸牌)

有n个小朋友坐成一圈,每人有若干颗糖果,每人只能给左右两人传递糖果。每人每次传递一颗糖果的代价为1。求使所有人获得均等糖果的最小代价。

与均分纸牌不同的是,我们要确定一个k,在第k个人之后把环断开形成一条链,因此要让 k 与 k+1 位置上没有发生变换。此时 k+1 作为第一个元素 A数组与前缀和为:

AS
A[k+1]s[k+1]-s[k]
A[k+2]s[k+2]-s[k]
…………
A[M]s[M]-s[k]
A[1]s[1]+s[M]-s[k]
…………
A[k]s[k]+s[M]-s[k]

因为均分之后A数组每个数都为0,因此S[M]=0;

此时的答案为 sum|s[i]-s[k]|.

另外,k的值可以通过“货仓选址”选择,取中位数作为 s[k] 就能得到最优解。

例题:https://ac.nowcoder.com/acm/problem/50172

#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1000010;
typedef long long LL;
LL a[maxn];
LL s[maxn];

int main(){
    ios::sync_with_stdio(false);
    int n;
    LL ans=0;
    LL t = 0;
    cin >> n;
    for (int i = 1; i <= n; i++){
        cin >> a[i];
        t += a[i];
    }
    t /= n;
    for (int i = 1; i <= n; i++){
        a[i] -= t;
        s[i] = s[i - 1] + a[i];
    }
    sort(s+1, s + n+1);
    int k = (n + 1) / 2;
    for (int i = 1; i <= n; i++){
        ans += abs(s[i] - s[k]);
    }
    cout << ans << endl;
}
0.5.3 第k大数

当我们求第k大数时,可以使用快速排序的思想,选取一个基准值后统计大于基准值的数量cnt,如果k<=cnt,那我们就在小于等于基准值的数中寻找第k大数,反之就在大于等于基准值的数中寻找第 k-cnt 大数

0.5.4 逆序对

求逆序对可以用归并排序的方法。

#include<iostream>
using namespace std;
const int maxn = 500010;
int a[maxn];
int b[maxn];
int cnt;

void merge(int l,int mid,int r){
    //合并a[l~mid] 与 a[mid+1~r]
    //a待排数组 b临时数组 cnt逆序对个数
    int i = l, j = mid + 1;
    for (int k = l; k <= r; k++){
        if( j>r || i<=mid && a[i] <=a[j] ){
            b[k] = a[i++];
        }else{
            b[k] = a[j++];
            cnt += mid - i + 1;
        }
    }
    for (int k = l; k <= r; k++){
        a[k] = b[k];
    }
}

void merge_sort(int l,int r){
    if(l>=r)
	return;
	int mid=(l+r)/2;
	merge_sort(l,mid);
	merge_sort(mid+1,r);
	merge(l,mid,r);	
}

int main(){
    ios::sync_with_stdio(false);
    int n;
    int d = 2;
    cin >> n;
    while(n){
        cnt =0;
        for (int i = 1; i <= n; i++){
            cin >> a[i];
        }
        merge_sort(1, n);
        cout << cnt << endl;

        cin >> n;
    }
}
0.6 倍增

例题:给定一个序列,然后进行若干次询问,每次给定一个整数T,求满足 sum(A[i]~A[k])<=T的最大k值

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
const int n = 10;
int a[n+1] = {0 ,1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int s[n+1];

int main(){
    int T;

    //预处理前缀和
    for (int i = 1; i <=10;i++){
        s[i] = s[i - 1] + a[i];
    }

    while( cin >> T ){
        int p = 1;
        int k = 0;
        int sum = 0;
        while(p){
            if(k+p>n)
                p = n - k;//边界判定
            if( sum+s[k+p]-s[k]<=T ){
                sum += s[k + p] - s[k];
                k = k + p;
                p *= 2;//满足条件,倍数翻倍
            }else{
                p /= 2;//不满足条件倍数减半
            }
        }
        cout << k << endl;
    }
}

ST算法

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
const int n = 10;
const int maxn = 1e3;
int a[n+5] = {0 ,1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int f[maxn][maxn];//±íʾÇø¼ä[i,i+2^j-1]ÀïÊýµÄ×î´óÖµ

void ST_prework(){
    for (int k = 1; k <= n; k++){
        f[k][0] = a[k];
    }
    int t = log(n) / log(2) + 1;//jµÄ×î´óÖµ
    for (int j = 1; j < t; j++){
        for (int i = 1; i <= n - (1 << j) + 1;i++){
            f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
        }
    }
}

int ST_query(int l,int r){
    int k = log(r - l + 1) / log(2);
    return max(f[l][k], f[r - (1 << k) + 1][k]);
}

//Çø¼äÇó×îÖµ
int main(){
    int l;
    int r;
    ST_prework(); //STÔ¤´¦Àí
    while( cin >> l >> r ){
        cout << ST_query(l, r)<<endl;
    }
}

0.7 贪心

例题:畜栏预订POJ3190

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

using namespace std;
typedef pair<int, int> p;
const int maxn = 50010;

int n;
int id[maxn];
pair<p, int> cows[maxn];

int main(){
    cin >> n;
    for (int i = 0; i < n; i++){
        cin >> cows[i].first.first >> cows[i].first.second;//进出时间
        cows[i].second = i;//编号
    }

    sort(cows, cows + n);

    priority_queue<p, vector<p>, greater<p>> heap;
    for (int i = 0; i < n; i++){
        //所有畜栏的最后一头牛结束吃草的时间是否比当前牛吃草时间早
        if( heap.empty() || heap.top().first >= cows[i].first.first ){
            p stall = {cows[i].first.second, heap.size()};//新建一个畜栏
            id[cows[i].second] = stall.second;
            heap.push(stall);
        }else{
            auto stall = heap.top();//把牛置入堆顶的畜栏
            heap.pop();
            stall.first = cows[i].first.second;
            id[cows[i].second] = stall.second;
            heap.push(stall);
        }
    }
    cout << heap.size() << endl;
    for (int i = 0; i < n; i ++ ) cout << id[i] + 1 << endl;    

}

例题:雷达定位 POJ1328

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
typedef pair<double, double> Pair;
const int maxn = 1010;
Pair a[maxn];//点坐标
Pair b[maxn];//区间范围

bool cmp(Pair a,Pair b){
    return a.second < b.second;
}
int main(){
    int n, d;
    while( cin >> n >> d && n+d){
        double x, y;
        int flag = 1;
        d *= d;
        for (int i = 0; i < n; i++){
            cin >> x >> y;
            a[i].first = x;
            a[i].second = y;
            y *= y;
            if( y>d )
                flag = 0;
            b[i].first = x - sqrt(d-y);
            b[i].second = x + sqrt(d - y);
        }
        if(flag){
            sort(b, b + n,cmp);
            int cnt = 0;
            double last = 0;
            for (int i = 0; i < n; i++){
                if( !i || !(b[i].first <=last ) ){
                    last = b[i].second;
                    cnt++;
                }
            }
                cout << cnt << endl;
        }else{
            cout << -1 << endl;
        }

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
算法竞赛进阶指南》是一本进阶级别的书籍,不适合初学者阅读。根据引用中的描述,每一章都会总结书中的难点知识,并附上例题和习题。从引用的目录可以看出,《算法竞赛进阶指南》包含了基本算法、位运算、递推与递归、前缀和与差分、二分、排序、倍增、贪心等内容,还包括了基本数据结构如栈、队列、链表、Hash、字符串、Trie、二叉堆等。此外,书中还讲解了动态规划的各种子领域,如线性dp、背包、区间dp、树形dp、状态压缩dp等。对于想要深入学习算法竞赛的读者来说,《算法竞赛进阶指南》是一本很好的参考书籍。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【算法竞赛进阶指南学习笔记](https://blog.csdn.net/cpp_juruo/article/details/122520206)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [算法竞赛进阶指南总结(一)](https://blog.csdn.net/weixin_64393298/article/details/124234703)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值