题解:P16680 [CSPro 27] 高维亚空间超频物质变压缩技术
lailai0916 · · 题解
题意简述
把数组划分为若干连续段。每段代价是该段元素和与
解题思路
记体积的前缀和为:
令
补充边界状态
直接枚举
每个已计算的决策点
计算
用树状数组维护
因此,树状数组的每个结点维护一组直线。单点加入一条直线时,沿 add 路径把它放入 sum 路径询问
还要快速维护每个结点中的直线集合。因为所有
每个树状数组结点都可以保存一个下凸包。插入时不断删除末尾的冗余直线。查询时用一个指针比较当前直线和下一条直线,下一条不劣时就右移。某条直线在每个凸包中至多加入、删除一次,查询指针也只会单向移动。
设三条直线
这样不需要浮点除法。代价和交叉乘积可能超过 long long。因此,动态规划值、前缀和与直线参数都使用 __int128。
树状数组的每次加入和查询会访问
正确性证明
先证明动态规划转移。任意合法划分都存在唯一的最后一段
再证明树状数组查询范围。决策点
最后证明凸包查询。平方展开后,决策点
树状数组覆盖全部且仅有合法决策点,每个凸包又准确返回局部最小值。因此,算法得到的
参考代码
#include <bits/stdc++.h>
using namespace std;
__extension__ using i128=__int128;
const int N=100005;
const i128 inf=i128(1)<<120;
struct Line
{
i128 k,b;
};
i128 calc(Line a,i128 x)
{
return a.k*x+a.b;
}
bool bad(Line a,Line b,Line c)
{
return (b.b-a.b)*(b.k-c.k)>=(c.b-b.b)*(a.k-b.k);
}
struct Hull
{
vector<Line> q;
int pos=0;
void add(Line a)
{
while(q.size()>=2&&bad(q[q.size()-2],q.back(),a))
{
q.pop_back();
if(pos==q.size())pos--;
}
q.push_back(a);
}
i128 ask(i128 x)
{
if(q.empty())return inf;
while(pos+1<q.size()&&calc(q[pos+1],x)<=calc(q[pos],x))pos++;
return calc(q[pos],x);
}
};
struct BIT
{
Hull c[N];
void add(int u,Line a){while(u<N){c[u].add(a);u+=u&-u;}}
i128 sum(int u,i128 x){i128 res=inf;while(u){res=min(res,c[u].ask(x));u-=u&-u;}return res;}
}T;
int m[N];
i128 s[N],f[N];
void print(i128 x)
{
if(x>=10)print(x/10);
cout<<int(x%10);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,L;
cin>>n>>L;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
s[i]=s[i-1]+x;
}
for(int i=1;i<=n;i++)cin>>m[i];
T.add(1,{0,0});
for(int i=1;i<=n;i++)
{
f[i]=(s[i]-L)*(s[i]-L)+T.sum(m[i],s[i]);
T.add(m[i]+1,{-2*s[i],f[i]+s[i]*s[i]+2*L*s[i]});
}
print(f[n]);
cout<<'\n';
return 0;
}