CF EDU164-E-數論分塊

yoshinow2001發表於2024-05-01

link:https://codeforces.com/contest/1954/problem/E

有一排怪物,第 \(i\) 只有 \(a_i\) 的血,每次攻擊可以選擇在 \(i\) 處放一個技能,技能會一直向左/右以相同的 \(k\) 點傷害擴散,直至遇到邊界或已經死亡的怪物。問最少需要放幾次技能?
需要對所有 \(k\) 回答答案。
\(n,a_i\leq 10^5\).


這種對於所有 xxx 值回答的問題,恐怕都要先把原問題做一個轉換,使其可以快速回答.

這裡我們不失一般性地考察 \(k=1\) 的情況,考慮這樣的特殊性並不是突然冒出來的動機,而是首先要注意任意一般的 \(k\) 都可以轉換為 \(k=1\) 的情形(無非是傷害變大 \(k\) 倍,把 \(a_i\to \lceil a_i/k\rceil\) 就好了),如果沒有意識到這樣一個特殊性和一般性之間的辯證關係,恐怕是比較難入手。

而對於 \(k=1\) 的情況:置 \(a_0\)\(0\),則答案應該是 \(\sum_{i=1}^n \max(0,a_i-a_{i-1})\),這個答案應該容易get到——想象 \(a\) 長成若干個山峰的樣子,肯定是先把最小值消掉,然後 \(a\) 被分成多個塊,每個塊內繼續消除。想象從 \(a\) 的最左爬到最右,影響答案的是一個個大大小小的山峰,這些山峰從低處到高處意味著需要操作,而高處回到低處則只是順帶消除掉。

那麼最後要計算的答案就是 \(\sum_{i=1}^n \max(0,\lceil \frac{a_i}{k}\rceil-\lceil \frac{a_{i-1}}{k}\rceil)\) ,而我們知道 \(\lceil a/k\lceil =\lfloor (a+k-1)/k\rfloor =1+\lfloor (a-1)/k\rfloor\),因此和下取整的除法分塊一樣,對每個 \(a_i-1\) 來說有 \(\Theta(\sqrt a_i)\)\(k\) 的值可能使其發生變化,預處理出這些 \(k\),然後計算答案。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
// #define endl '\n'
#define fastio ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=1e5+5;
int n,a[N],b[N];
vector<int> val_k[N],pos[N];
int main(){
    fastio;
    cin>>n;
    int mx=0;
    rep(i,1,n){
        cin>>a[i];
        mx=max(mx,a[i]);
        pos[a[i]-1].push_back(i);
    }

    rep(m,1,1e5){
        for(int i=1,pos;i<=m;i=pos+1){
            pos=min(m,m/(m/i));
            val_k[pos+1].push_back(m);
        }
    }
    ll S=0;
    rep(i,1,n){
        b[i]=max(0,a[i]-a[i-1]);
        S+=b[i];
    }
    cout<<S;
    rep(k,2,mx){   
        for(auto v:val_k[k]){
            for(auto p:pos[v]){
                if(p==1){
                    S-=b[1];
                    b[1]=max(0,(a[1]-1)/k+1);
                    S+=b[1];
                }else{
                    S-=b[p];
                    b[p]=max(0,(a[p]-1)/k-(a[p-1]-1)/k);
                    S+=b[p];
                }

                if(p==n)continue;

                S-=b[p+1];
                b[p+1]=max(0,(a[p+1]-1)/k-(a[p]-1)/k);
                S+=b[p+1];
            }
        }
        cout<<' '<<S;
    }
    return 0;
}