1. 程式人生 > >題解 【BZOJ4700】適者

題解 【BZOJ4700】適者

amp 攻擊 先後 i++ 滿足 tro code node 就會

題面

解析

看了好多dalao們的題解,然而還是不明白... 於是在想了半天後,決定自己寫一篇題解。

  • step 1

    首先,分析題意,
    應該還是比較容易想到,
    要一直攻擊一個兵器,
    直到破壞它為止。
    因為遲早要把兵器都破壞完,但如果不先破壞一個的話,他就會一直攻擊。
    那麽,就會有一個破壞兵器的先後順序。
    並且,註意到血量其實只是為了轉化成攻擊的次數,
    因此可以用\(d_i\)表示\(i\)被攻擊的次數,
    另外,約定\(a_i\)\(i\)的攻擊力,
    那麽題目就分析完畢了!似乎說了一通廢話
  • step 2

    先考慮沒有秒殺的情況,
    那麽,傷害最小,其實也就是\(\sum_{i=1}^{n}\) \(a_i\)
    \(*\) (\(\sum_{j=1}^{i}\)-\(1\)) 最小,(註意,由於最後一次攻擊時兵器已經被破壞了(題目描述有問題),因此要減\(1\))
    而對於兩個相鄰兵器\(i\),\(j\)
    如果\(i\)排在\(j\)前面會更優,
    並設\(X\) \(=\) \(\sum_{k=1}^{i}d_k\)
    則有不等式
    \(a_i\) \(*\) (\(X\)-\(1\))+$a_j $ \(*\) (\(X\)\(+\)\(d_i\) - \(1\))\(<\)\(a_j\) \(*\) (\(X\)-\(1\))+$a_i $ \(*\) (\(X\)\(+\)\(d_j\)
    - \(1\))
    那麽,可以解得\(d_i\)\(*\)\(a_j\)\(<\)\(d_j\)\(*\)\(a_i\),(自己解一下吧) 因此,將兵器按此規律排序,就是沒有秒殺的最優解了似乎又是廢話
    但要計算出這個沒有秒殺的最優解(後面要用)。
  • step 3

    繼續,在上一步的情況下考慮秒殺掉一個兵器,
    \(v_i\)為刪除\(i\)後能減小的傷害,
    那麽對於排在\(i\)前面的兵器們,
    在攻擊他們的時候,\(i\)都會對基地造成傷害,
    那麽對於這一部分,減少的傷害就為(\(\sum_{j=1}^{i}\)\(d_j\)-\(1\))\(*\)\(a_i\)
    而對於排在\(i\)後面的所有兵器,
    在攻擊\(i\)時,
    他們也在對基地造成傷害。
    所以對於這一部分,減少的傷害為(\(\sum_{j=i+1}^{n}\)\(a_j\))\(*\)\(d_i\)
    綜上,就可以得出\(v_i\)\(=\)(\(\sum_{j=1}^{i}\)\(d_j\)-\(1\))\(*\)\(a_i\)\(+\)(\(\sum_{j=i+1}^{n}\)\(a_j\))\(*\)\(d_i\)
    那麽繼續考慮秒殺兩個兵器\(i\)\(j\),且\(i\)\(j\)後面,
    減少的傷害就是\(v_i\)\(+\)\(v_j\)\(-\)\(a_i\)\(*\)\(d_j\)(因為這一部分重復加了一次,所以減掉)。
    然後,只要求出使\(v_i\)\(+\)\(v_j\)\(-\)\(a_i\)\(*\)\(d_j\)最大的\(i\),\(j\)就行了。
  • step 4

    那麽怎麽求呢?
    對於每個\(i\)
    我們計算當它與順序中前面的某個兵器一起被秒掉時,
    能減少的傷害的最大值,
    再算出\(1\)~\(n\)中這個式子最大的值就行了。
    那麽,我們要算出前面的兵器對它的影響,和它對後面的兵器的影響。
    所以\(CDQ\)分治就成了解決問題的主要算法。
    但還是無法解決問題啊!!!
    到底要怎麽算啊?
    別著急,看我狂推公式【滑稽】。
    首先我們設對於兵器\(i\),我們已經找到了一個\(j\)和它一起秒殺(重復一遍,\(j\)\(i\)前面),
    但是,卻存在一個\(k\)能使答案更優,即:
    \(v_i\)\(+\)\(v_k\)\(-\)\(a_i\)\(*\)\(d_k\)\(>=\)\(v_i\)\(+\)\(v_j\)\(-\)\(a_i\)\(*\)\(d_j\)
    那麽,約掉\(v_i\),再移下項,就變成了:
    (\(d_k\)-\(d_j\))\(*\)\(a_i\)\(<=\)\(v_k\)-\(v_j\)
    然後分類討論:
  1. \(d_k\)\(>\)\(d_j\)時,\(a_i\)\(<=\)\(\frac{v_k-v_j}{d_k-d_j}\)
  2. \(d_k\)\(=\)\(d_j\)時,\(v_k\)\(>=\)\(v_j\)
  3. \(d_k\)\(<\)\(d_j\)時,\(a_i\)\(>=\)\(\frac{v_j-v_k}{d_j-d_k}\)

因此,我們只需要找到一個\(j\),使得找不到\(k\)滿足上面的式子,
那麽這個\(j\)就是最優解了。
即:
\(y\)\(i\)的最優解 ,
那麽對於任意滿足\(d_x\)\(<\)\(d_y\)\(<\)\(d_z\)(等於時要特判)的\(x\),\(z\)
都有\(\frac{v_y-v_x}{d_y-d_x}\)\(>\)\(a_i\)\(>\)\(\frac{v_y-v_z}{d_y-d_z}\)
因此,對於\(l\)~\(mid\),我們先將\(d\)按升序排列,
再用單調隊列維護\(\frac{v_y-v_x}{d_y-d_x}\)的降序排列(蒟蒻的單調隊列講得似乎有點不清楚自己理解下哈)
同時,對於\(mid\)+\(1\)~\(r\)為了節省時間,將\(a\)按降序排列(所以多開幾個數組),
每次在單調隊列中找出第一個小於\(a_i\)\(\frac{v_y-v_x}{d_y-d_x}\)
再用\(y\)更新\(i\)的答案就行了。
對於\(d\)\(a\)的排序,歸並時排一下就可以解決。
具體實現方式看代碼吧:

#include<bits/stdc++.h>
#define db double
#define ll long long
using namespace std;

inline int read(){
    ll sum=0,f=1;char ch=getchar();
    while(ch>'9' || ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0' && ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
    return f*sum;
}

struct node{
    int a/*攻擊*/,d/*被打次數*/;
    ll v/*被秒掉後下降的傷害*/;
}c[500001]/*敵方*/,q1[500001]/*按d升序排列*/,q2[500001]/*按a降序排列*/;
node qq[500001];//排序時臨時存一下
int n,atk;
ll ans=0,ansn=0;
ll sa[500001]/*被打次數的前綴和*/,sb[500001]/*攻擊的後綴和*/;
int q[500001];

bool cmp(node x,node y){
    return (ll)x.d*y.a<(ll)y.d*x.a;
}

db cmp1(int x,int y){
    if(q1[x].d==q1[y].d){
        if(q1[y].v<=q1[x].v) return 0;
        return 1e18;
    }
    return (db)((q1[y].v-q1[x].v)/(q1[y].d-q1[x].d));
}

void CDQ(int l,int r){
    if(l==r) return ;
    int mid=(l+r)>>1;
    CDQ(l,mid);CDQ(mid+1,r);
    int le=1,ri=0;
    for(int i=l;i<=mid;i++){
        while(le<ri&&cmp1(q[ri-1],q[ri])<cmp1(q[ri],i)) ri--;
        q[++ri]=i;
    }
    for(int i=mid+1;i<=r;i++){
        while(le<ri&&cmp1(q[le],q[le+1])>=q2[i].a) le++;
        ans=max(ans,(ll)(q2[i].v+q1[q[le]].v-q2[i].a*q1[q[le]].d));
    }
    int pi=l,qi=mid+1,tot=l;
    while(pi<=mid&&qi<=r){
        if(q1[pi].d<q1[qi].d) qq[tot++]=q1[pi++];
        else qq[tot++]=q1[qi++];
    }
    while(pi<=mid) qq[tot++]=q1[pi++];
    while(qi<=r) qq[tot++]=q1[qi++];
    for(int i=l;i<=r;i++) q1[i]=qq[i];
    pi=l,qi=mid+1,tot=l;
    while(pi<=mid&&qi<=r){
        if(q2[pi].a>q2[qi].a) qq[tot++]=q2[pi++];
        else qq[tot++]=q2[qi++];
    }
    while(pi<=mid) qq[tot++]=q2[pi++];
    while(qi<=r) qq[tot++]=q2[qi++];
    for(int i=l;i<=r;i++) q2[i]=qq[i];
}

int main(){
    freopen("fight.in","r",stdin);
    freopen("fight.out","w",stdout);
    n=read();atk=read();
    for(int i=1;i<=n;i++){
        c[i].a=read();c[i].d=read();
        c[i].d=(c[i].d-1)/atk+1;
    }
    sort(c+1,c+n+1,cmp);
    for(int i=1;i<=n;i++) sa[i]=sa[i-1]+c[i].d;
    for(int i=n;i>=1;i--) sb[i]=sb[i+1]+c[i].a;
    for(int i=1;i<=n;i++) ansn+=c[i].a*(sa[i]-1);
    for(int i=1;i<=n;i++){
        c[i].v=(sa[i]-1)*c[i].a+sb[i+1]*c[i].d;
    }
    for(int i=1;i<=n;i++) q1[i]=c[i],q2[i]=c[i];
    CDQ(1,n);
    printf("%lld\n",ansn-ans);
    return 0;
}

題解 【BZOJ4700】適者