[BZOJ4700]适者 CDQ分治

本文解析了CDQ分治算法在解决特定问题时的应用,包括如何通过排序和单调队列来优化炮塔攻击顺序问题,减少总伤害值。文章详细介绍了算法实现步骤,并提供了完整的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

看到题目以后并没有第一时间想到cdq分治,看了很多篇题解都没有明白,后来自己yy出来了一个解法,应该是对的反正过了

首先考虑若一个炮塔都没有被秒杀,那么怎么安排攻击顺序最好
对于两个炮塔u,v,若u在前那么v将多产生 d[u]×a[v] 的伤害,v在u前那么u将多产生 d[v]×[u] ,显然我们要使伤害最小,即

当u在v前时,满足 d[u]×a[v]<d[v]×a[u] ,整理得 d[u]a[u]<d[v]a[v] ,等式两边都只与u或者v相关,满足偏序,排序一发即可。

再来考虑从中拿两个走,记拿x走总伤害减少 c[x]
我们需要找到最大的 i ,j使得在第一次排序中 i j前面,且 c[i]+c[j]d[i]×a[j] 最小

考虑对于每一个 j ,寻找能使上述表达式值最大的i
即使得 c[i]d[i]×a[j] ,将它看成 a[j] 为自变量的一次函数,最后的取值显然是一个下突壳,单调队列维护即可,CDQ分治递归下去即可

可能说的有点抽象,给一下主程序的步骤:

1、递归处理左右区间
2、右区间按a[]排升序,即自变量从小到大
3、左区间加入单调队列
4、两个指针扫一下,把左区间的信息加到右区间上
5、整个区间按d[]排序,即按斜率排升序(注意这里斜率取负数)

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>

#define N 300050
#define mid ((l + r) >> 1)
#define INF (1LL<<62)

using namespace std;
typedef long long LL;
struct Monster{ LL a,d,c,_; }x[N],tmp[N];
int q[N],h,t,n,ATK;
LL sum_d[N]; 
inline void ut(LL &x,LL y) { x = min(x,y); }
inline int rd() { int r; scanf("%d",&r); return r; }

bool cmp1(Monster p1,Monster p2) {
    return p1.d * p2.a < p2.d * p1.a;
}
bool cmp2(Monster p1,Monster p2) { return p1.a < p2.a; }

void _sort1(int l,int r) {
    int p1 = l , p2 = mid+1 , cur = l-1;
    while (p1<mid && p2<r) {
        if (x[p1].a < x[p2].a)
            tmp[++cur] = x[p1] , p1++;
        else
            tmp[++cur] = x[p2] , p2++;
    }
    while (p1 <= mid) tmp[++cur] = x[p1] , p1++;
    while (p2 <= r) tmp[++cur] = x[p2] , p2++;
    for (int _=l;_<=r;_++) x[_] = tmp[_];
}

void _sort2(int l,int r) {
    int p1 = l , p2 = mid+1 , cur = l-1;
    while (p1<mid && p2<r) {
        if (x[p1].d < x[p2].d)
            tmp[++cur] = x[p1] , p1++;
        else
            tmp[++cur] = x[p2] , p2++;
    }
    while (p1 <= mid) tmp[++cur] = x[p1] , p1++;
    while (p2 <= r) tmp[++cur] = x[p2] , p2++;
    for (int _=l;_<=r;_++) x[_] = tmp[_];
}

inline LL calc(int p1,int p2) {
    return x[p1].c + x[p2].c + x[p1].d * x[p2].a;
}

void solve(int l,int r) {
    if (l == r) return ;
    solve(l,mid); solve(mid+1,r);

    _sort2(mid+1,r);

    q[h=t=1] = l;
    for (int i=l+1;i<=mid;i++) {
        while (h<=t && x[i].c > x[ q[t] ].c) t--;
        q[++t] = i;
    }

    for (int i=mid+1;i<=r;i++) {
        while (h<t && calc(q[h],i) < calc(q[h+1],i)) h++;
        x[i]._ = max( x[i]._ , calc(q[h],i) );
    }

    _sort1(l,r);
}
//36+42+30
int main() {
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
    #endif
    n = rd() , ATK = rd();
    for (int i=1;i<=n;i++) x[i].a = rd() , x[i].d = rd() , x[i]._ = -INF;
    for (int i=1;i<=n;i++) x[i].d = (int) ceil( (double)x[i].d / (double)ATK );
    sort(x+1,x+n+1,cmp1);

    LL cur = 0LL , sum = 0LL;
    for (int i=1;i<=n;i++) sum_d[i] = sum_d[i-1] + x[i].d;
    for (int i=n;i>=1;i--) {
        x[i].c = x[i].d * cur + x[i].a * (sum_d[i]-1);
        sum +=  x[i].d * cur + x[i].a * (x[i].d-1);
        cur += x[i].a;
    }

    //转成斜率 
    for (int i=1;i<=n;i++) x[i].d = -x[i].d;
//  sort(x+1,x+n+1,cmp2);
    solve(1,n);

    LL ans = INF;
    for (int i=1;i<=n;i++) ut(ans,sum-x[i]._);
    cout << ans << endl;
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值