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;
}