题解:P11677 [USACO25JAN] Shock Wave P
场上奋战两个半小时,最后二十分钟推出来性质,最后获得零分。
这个题要你刻画的是若干个绝对值函数的加和的性质。
我们把问题变成维护一个一开始全为 0 的序列
既然刻画不明白,我们先考虑一些简单的性质。首先我们可以发现一件事情是你操作不同的位置,对
然后你发现如果同时在
继续观察。注意到如果你同时选了
很可惜这个结论没有用,但是令人惊讶的是这个结论是可以推广的,具体来说,如果你操作了两个位置
由此归纳出一个结论:在最优解中一定是选了若干个
问题变成下面这个形式,设
我们先考虑暴力是怎么做,首先枚举一个
接下来我们观察到第二个性质:如果规定只能操作
这个结论是显然的,因为选择的
我们可以先二分答案求出来只用
我们希望对于不同的
接下来考虑
具体来说我们可以用一个优先队列维护当前最严格的限制,这样做时间复杂度是
实际上
代码正在赶工中,因为负数除负数上下取整实在写不明白了。
upd on 2.6: 奋战四个小时终于把代码写出来了,感觉其实和季风差不多,但是写不明白啊。
//非常优美的题,但是代码太毒瘤了
#include<bits/stdc++.h>
using namespace std;
#define int long long
int Abs(__int128 x){if(x<0)return -x;return x;}
__int128 chu(__int128 x,int y){
//向上取整
return (x+y-1)/y;
}
__int128 Chu(__int128 x,int y){
//向下取整
if(x<=0 and y<0)return (-x)/(-y);
else return -1;
}
int n;
__int128 a[500005];int b[500005];
bool check(__int128 x){
__int128 L = 0,R = x;
for(int i = 0;i<=n;i++){
if(2*i == n){
if(x*(n-i)<a[i])return 0;
continue;
}
if(2*i<n){
//是一个负数
__int128 C = a[i]-(n-i)*x;
R = min(R,Chu(C,2*i-n));
}else{
__int128 C = a[i]-(n-i)*x;
L = max(L,chu(C,2*i-n));
}
}
return (L<=R);
}
struct heap{
priority_queue<int,vector<int>,greater<int> >q,del;
void clear(){while(q.size())q.pop();while(del.size())del.pop();}
void Delete(int x){del.push(x);}
void Insert(int x){q.push(x);}
int top(){
while(del.size() and q.top() == del.top())q.pop(),del.pop();
return q.top();
}
}ql,qr;
vector<int>addl[500005],dell[500005],addr[500005],delr[500005];
void Addl(int l,int r,int x,int id){
//给一段区间加上 x 的限制
addl[l].push_back(-x),dell[r+1].push_back(-x);
}
void Addr(int l,int r,int x,int id){
addr[l].push_back(x),delr[r+1].push_back(x);
}
int work(int X){
ql.clear(),qr.clear();
ql.Insert(0),qr.Insert(X);
for(int i =0;i<=n+1;i++)addl[i].clear(),dell[i].clear(),addr[i].clear(),delr[i].clear();
int i = n;
for(int i = 0;i<=n;i++)a[i] -= (n-i)*X;
while(2*i>n){
// n-2i
int now = i;
while(now<=n){
// (0,a] 算一段
//算出和这个点值相同的最远的点
__int128 C = a[i]-abs(now-i),to = 0;
//对于更远的点 C 的数值会减小
if(C<=0){
to = 0;
}else{
to = C/(2*i-n)*(2*i-n)+1;
if(C%(2*i-n) == 0)to = C-(2*i-n)+1;
}
int r = min(Abs(to-C)+now,n);
if(C>0){
Addl(now,r,chu(C,i*2-n),i);
}
now = r+1;
}
now = i-1;
while(now>=0){
__int128 C = a[i]-abs(now-i),to = 0;
if(C<=0){
to = 0;
}else{
to = C/(n-2*i)*(n-2*i)+1;
if(C%(2*i-n) == 0)to = C-(2*i-n)+1;
}
int l = max(now-Abs(to-C),0ll);
if(C>0){
Addl(l,now,chu(C,i*2-n),i);
}
now = l-1;
}
--i;
}
i = 0;
while(2*i<n){
int now = i;
while(now<=n){
__int128 C = a[i]-abs(now-i),to = 0;
if(C<=0){
to = -C;
if(to%(n-2*i)!=0)to += (n-2*i)-to%(n-2*i);
else to += (n-2*i);
--to;
to = -to;
}else{
to = 0;
}
int r = min(Abs(to-C)+now,n);
if(C>0)Addr(now,r,-1,i);
else Addr(now,r,Chu(C,i*2-n),i);
now = r+1;
}
now = i-1;
while(now>=0){
__int128 C = a[i]-abs(now-i),to = 0;
if(C<=0){
to = -C;
if(to%(n-2*i)!=0)to += (n-2*i)-to%(n-2*i);
else to += (n-2*i);
--to;
to = -to;
}else{
to = 0;
}
int l = max(now-Abs(to-C),0ll);
if(C>0)Addr(l,now,-1,i);
else Addr(l,now,Chu(C,i*2-n),i);
now = l-1;
}
++i;
}
for(int i = 0;i<=n;i++){
for(auto x:addl[i])ql.Insert(x);
for(auto x:dell[i])ql.Delete(x);
for(auto x:addr[i])qr.Insert(x);
for(auto x:delr[i])qr.Delete(x);
if(n%2 == 0 and (a[n/2]-abs(n/2-i))>0)continue;
int L = -ql.top(),R = qr.top();
if(L<=R)return 1;
}
return 0;
}
void solve(){
cin >> n;--n;
for(int i = 0;i<=n;i++)cin >> b[i];for(int i = 0;i<=n;i++)a[i] = b[i];
__int128 sum = 0;int mx = 0;for(int i = 0;i<=n;i++)sum += b[i],mx = max(mx,b[i]);
int l = sum/n/n,r = 2*(mx+n-1)/n;
while(l<r){
int mid = l+r>>1;
if(check(mid))r = mid;
else l = mid+1;
}
int ans = r;
if(ans>1 and work(ans-2))--ans;
cout << ans << '\n';
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t;cin >> t;;
while(t--)solve();
return 0;
}/*
*/