四边形不等式
写的有点答辩了。
最简单的一种:
2D1D的状态转移方程:
当 \(w(l,r)\) 满足一些性质时能利用决策单调性优化。
-
区间包含单调性:\(\forall\space l\le l'\le r'\le r,w(l',r')\le w(l,r)\) ,那么 \(w\) 对于区间包含关系具有单调性。
-
四边形不等式:\(\forall\space l_1\le l_2\le r_1\le r_2,w(l_1,r_1)+w(l_2,r_2)\le w(l_1,r_2)+w(l_2,r_1)\) (交叉小于包含),则 \(w\) 满足四边形不等式。等号恒成立称 \(w\) 满足 四边形恒等式。
- 若对于定义域上的任意整数 \(a<b\),有 \(w(a,b+1)+w(a+1,b)\ge w(a,b)+w(a+1,b+1)\),则 \(w\) 满足四边形不等式。
对于 \(a<c\),有 \(w(a,c+1)+w(a+1,c)\ge w(a,c)+w(a+1,c+1)\)
对于 \(a+1<c\),有 \(w(a+1,c+1)+w(a+2,c)\ge w(a+1,c)+w(a+2,c+1)\)
相加得到 \(w(a,c+1)+w(a+2,c)\ge w(a,c)+w(a+2,c+1)\)
即对于任意 \(a\le b\le c\),有 \(w(a,c+1)+w(b,c)\ge w(a,c)+w(b,c+1)\) .
同理对于任意 \(a\le b\le c\le d\),有 \(w(a,d)+w(b,c)\ge w(a,c)+w(b,d)\) .
- 若 \(w(l,r)\) 满足区间包含单调性和四边形不等式,则 \(f(l,r)\) 满足四边形不等式。
太长了不放,感性理解。
- 若状态 \(f\) 满足四边形不等式,记 \(m_{l,r}=\min\{k:f_{l,r}=g_{k,l,r}\}\) 为最优决策点,则
记 \(u=m_{l,r},k_1=m_{l,r-1},k_2=m_{l+1,r}\) ,即证明 \(k_1\le u\le k_2\) .
\(1.\) 若 \(k_1>u\) ,则 \(u+1\le k_1+1\le r-1\le r\) ,有
因为 \(u\) 是 \(f_{l,r}\) 的最优决策点:
两式相加可得
即 \(g_{u,l,r-1}\le g_{k_1,l,r-1}\) ,那么 \(u=m_{l,r-1}\) ,与 \(k_1>u\) 矛盾。
所以 \(k_1\le u\) .
\(2.\) 若 \(u>k_2\) ,则 \(l\le l+1\le k_2\le u\) ,有
因为 \(k_2\) 是 \(f_{l+1,r}\) 的最优决策点:
两式相加可得
即 \(g_{k_2,l,r}\le g_{u,l,r}\) ,那么 \(k_2=m_{l,r}\) ,与 \(u>k_2\) 矛盾。
所以 \(u\le k_2\) .
综上可得 \(k_1\le u\le k_2\) ,即 \(m_{l,r-1}\le m_{l,r}\le m_{l+1,r}\) .
得到决策单调性就可以进行DP了。
当计算 \(f_{l,r}\) 时,将其最优决策点 \(m_{l,r}\) 记录下来,那么对决策点的枚举总量为
长这个样子。
for(int len=2;len<=n;len++)
for(int l=1,r=len;r<=n;l++,r++){
f[l][r]=inf;
for(int k=m[l][r-1];k<=m[l+1][r];k++)
if(f[l][r]>f[l][k]+f[k+1][r]+w(l,r)){
f[l][r]=f[l][k]+f[k+1][r]+w(l,r);
m[l][r]=k;
}
}
写了没保存。
数轴上 \(n\) 个点 \(\{a\}\) ,放 \(m\) 个点,最小化
\(n\le 3000,m\le300\) .
先将 \(a\) 升序排序。
设 \(f(i,j)\) 表示考虑前 \(i\) 个点,放置 \(j\) 个点的最小值。
那么
其中 \(w(l,r)\) 指在这段区间放一个点的最小距离和。
显然这个点应该在区间的中位数处,可以暴力做。
复杂度为 \(O(n^3m)\) .
考虑一次转移能不能做到 \(O(1)\) ,能发现 \(w\) 是可以 \(O(n^2)\) 预处理出来的,有
但是复杂度仍为 \(O(n^2m)\) .
想一下 \(w\) 是否满足四边形不等式。
由递推式得
两式相减有
由于坐标递增得上式 \(\le 0\) .
所以
故 \(f\) 满足四边形不等式,于是可以利用决策单调性进行优化。
时间复杂度 \(O(nm)\) .
#include<bits/stdc++.h>
#define N 3010
#define M 305
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,m,a[N];
int w[N][N],f[N][M],b[N][M];
void init(){
for(int l=1;l<=n;l++){
w[l][l]=0;
for(int r=l+1;r<=n;r++)
w[l][r]=w[l][r-1]+a[r]-a[(l+r)>>1];
}
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)
a[i]=read();
sort(a+1,a+1+n);
memset(f,0x3f,sizeof(f));
init(),f[0][0]=0;
for(int j=1;j<=m;j++){
b[n+1][j]=n;
for(int i=n;i;i--){
for(int k=b[i][j-1];k<=b[i+1][j];k++)
if(f[i][j]>f[k][j-1]+w[k+1][i])
f[i][j]=f[k][j-1]+w[k+1][i],b[i][j]=k;
}
}
printf("%d\n",f[n][m]);
return 0;
}
P3515 [POI2011]Lightning Conductor
序列 \(\{a_n\}\),对于每个 \(i\in\lbrack1,n\rbrack\),求出最小的非负整数 \(p\) 满足 \(\forall j\in\lbrack1,n\rbrack\),\(a_j\le a_i+p-\sqrt{|i-j|}\) .
\(n\le 5\times10^5\),\(0\le a_i\le 10^9\) .
正着做一次,翻转序列后再做一次就可以消掉绝对值。
设 \(f(i)=\max\{a_j+\sqrt{i-j}\}\),\(w(j,i)=\sqrt{i-j}\) .
令 \(l+1<r\):
令 \(t=r-l\):
这个式子是 \(<0\) 的。
那么 \(\forall a\le b\le c\le d\),有 \(w(a,d)+w(b,c)\le w(a,c)+w(b,d)\) .
本题求 \(\max\),将符号取反,可以证明 \(f\) 满足决策单调性。
定义 \(P_i\) 为 \(i\) 的最优决策点。
对于 \(\forall j\in\lbrack0,P_i-1\rbrack\),有
因为 \(w\) 满足四边形不等式,对于 \(\forall i'\in\lbrack i+1,n\rbrack\),有
与第一个不等式相加
即 \(i'\) 的最优决策点一定 \(\ge P_i\),\(f\) 具有决策单调性。
求出 \(f(i)\) 是考虑其可以作为哪些 \(f(i')\) 的最优决策。
根据决策单调性,可以找到一个 \(pos\),这之前的决策都比 \(i\) 优,后面的都比 \(i\) 差。
将该位置及其后面的部分都变为 \(i\),此时它们的最优决策点均为 \(i\) .
建立一个队列代替 \(P\),保存三元组 \((j,l,r)\),表示 \(P(l\)~\(r)\) 都是 \(j\) .
对于每个 \(i\),执行如下操作:
\(1.\) 设队头为 \((j_0,l_0,r_0)\),若 \(r_0=i-1\) 则删除队头,因为 \(f(i)\) 之前的值都已求出,否则令 \(l_0=i\) .
\(2.\) 取队头的最优决策 \(j\) 转移算出 \(f(i)\) .
\(3.\) 插入新决策 \(i\):
\(\enspace (1).\) 取出队尾 \((j_t,l_t,r_t)\) .
\(\enspace (2).\) 若对于 \(f(l_t)\),\(i\) 比 \(j_t\) 更优,即 \(f(i)+w(i,l_t)\le f(j)+w(j_t,l_t)\),记 \(pos=l_t\),删除队尾返回到 \((1)\) .
\(\enspace (3).\) 若对于 \(f(r_t)\),\(i\) 比 \(j_t\) 更优,去往 \((5)\) .
\(\enspace (4).\) 否则,在 \(\lbrack l_t,r_t\rbrack\) 上二分出 \(pos\),在此之前决策优于 \(i\),后面劣于 \(i\),将 \(\lbrack l_t,r_t\rbrack\) 改为 \(\lbrack l_t,pos-1\rbrack\),去往 \((5)\) .
\(\enspace (5).\) 将三元组 \((i,pos,n)\) 插入队尾。
时间复杂度 \(O(n\log n)\) .
这题把符号取反就好。
#include<bits/stdc++.h>
#define db double
#define N 500010
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,a[N],h,t;
db f[N],sqr[N];
struct node{
int l,r,p;
}q[N];
db w(int j,int i){
return db(a[j])+sqr[i-j];
}
int find(int t,int x){
int pos=q[t].r+1,l=q[t].l,r=q[t].r,mid;
while(l<=r){
mid=(l+r)>>1;
if(w(q[t].p,mid)<=w(x,mid))pos=mid,r=mid-1;
else l=mid+1;
}
return pos;
}
void insert(int i){
q[t].l=max(q[t].l,i);
while(h<=t&&w(i,q[t].l)>=w(q[t].p,q[t].l))t--;
if(h>t)q[++t]=(node){i,n,i};
else{
int pos=find(t,i);
if(pos>n)return;
q[t].r=pos-1;
q[++t]=(node){pos,n,i};
}
}
void work(){
h=1,t=0;
for(int i=1;i<=n;i++){
insert(i);
if(h<=t&&q[h].r<i)h++;
else q[h].l=i;
f[i]=max(f[i],w(q[h].p,i));
}
}
int main(){
n=read();
for(int i=1;i<=n;i++){
a[i]=read();
sqr[i]=sqrt(i);
}
work();
for(int i=1;i<=n/2;i++)
swap(a[i],a[n-i+1]),swap(f[i],f[n-i+1]);
work();
for(int i=n;i;i--)
printf("%d\n",(int)ceil(f[i])-a[i]);
return 0;
}
没总结出来比较干练的dp模板。
\(n\) 个字符串,长度分别为 \(a_i\),将它们按顺序划分成若干段,一段区间 \(\lbrack l,r\rbrack\) 的代价是
试最小化总代价并给出方案。多测。
-
若答案大于 \(10^{18}\) 输出
Too hard to arrange
. -
极限数据有 \(T\le5\),\(n\le10^5\),\(L\le3\times10^6\),\(P\le10\).
-
所有 \(a_i\) 都 \(\le30\).
默认 \(a_i+1\),记 \(s_x=\sum_{i=1}^{x}a_i\).
\(\rm dp\) 方程是
这个东西是高次的所以斜优不了。
若证明 \(w(j,i)=|s_i-s_j-(L+1)|^P\) 满足四边形不等式,就可以用单调队列解决。
设
由于 \(u\ge v\),即证 \(g(x)=|x|^P-|x+z|^P(z\in\lbrack0,+\infty))\) 非严格单调递减。
拆一下绝对值。
- \(x\in\lbrack0,\infty)\):肉眼看比较显然。
求导?
容易发现 \(g'(x)<0\).
- \(x\in(-\infty,0),P\equiv0\space(\text{mod}\space 2)\)
就是 \((-x)^P-(-x+z)^P\),和上面一样是递减的。
- \(x\in\lbrack-z,0),P\equiv1\space(\text{mod}\space 2)\)
\(P\) 为奇数所以 \(g'(x)\le 0\).
- \(x\in(-\infty,-z),P\equiv 1\space(\text{mod}\space 2)\)
同样 \(g'(x)\le 0\).
不知道为什么要这么严谨证。
把流程再放一遍。还是不太会。
建立一个队列代替 \(P\),保存三元组 \((j,l,r)\),表示 \(P(l\)~\(r)\) 都是 \(j\) .
对于每个 \(i\),执行如下操作:
\(1.\) 设队头为 \((j_0,l_0,r_0)\),若 \(r_0=i-1\) 则删除队头,因为 \(f(i)\) 之前的值都已求出,否则令 \(l_0=i\) .
\(2.\) 取队头的最优决策 \(j\) 转移算出 \(f(i)\) .
\(3.\) 插入新决策 \(i\):
\(\enspace (1).\) 取出队尾 \((j_t,l_t,r_t)\) .
\(\enspace (2).\) 若对于 \(f(l_t)\),\(i\) 比 \(j_t\) 更优,即 \(f(i)+w(i,l_t)\le f(j)+w(j_t,l_t)\),记 \(pos=l_t\),删除队尾返回到 \((1)\) .
\(\enspace (3).\) 若对于 \(f(r_t)\),\(i\) 比 \(j_t\) 更优,去往 \((5)\) .
\(\enspace (4).\) 否则,在 \(\lbrack l_t,r_t\rbrack\) 上二分出 \(pos\),在此之前决策优于 \(i\),后面劣于 \(i\),将 \(\lbrack l_t,r_t\rbrack\) 改为 \(\lbrack l_t,pos-1\rbrack\),去往 \((5)\) .
\(\enspace (5).\) 将三元组 \((i,pos,n)\) 插入队尾。
时间复杂度 \(O(Tn\log n)\).
答案大于 \(10^{18}\) 的时候会炸 \(\rm long\space long\),要开 \(\text{long double}\) 牺牲精度换取值域。
贺了一个长得比较像斜优的题解。
#include<bits/stdc++.h>
#define ll long long
#define lb long double
#define N 100010
#define R 32
using namespace std;
char st[N][R];
int T,n;
ll L,P,s[N];lb f[N];
int h,t,q[N],pre[N];
int stk[N],top;
lb qpow(lb k,ll b){
lb ret=1;
while(b){
if(b&1)ret*=k;
b>>=1,k*=k;
}
return ret;
}
lb calc(int j,int i){
return f[j]+qpow(abs(s[i]-s[j]-L),P);
}
int find(int t,int x){
if(calc(t,n)<calc(x,n))return n+1;
int pos=-1,l=x,r=n,mid;
while(l<=r){
mid=(l+r)>>1;
if(calc(x,mid)<=calc(t,mid))pos=mid,r=mid-1;
else l=mid+1;
}
return pos;
}
void work(){
h=1,t=0,q[++t]=0;
for(int i=1;i<=n;i++){
while(h<t&&find(q[h],q[h+1])<=i)h++;
pre[i]=q[h],f[i]=calc(q[h],i);
while(h<t&&find(q[t-1],q[t])>=find(q[t],i))t--;
q[++t]=i;
}
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%lld%lld",&n,&L,&P),L++;
for(int i=1;i<=n;i++){
scanf("%s",st[i]);
s[i]=s[i-1]+strlen(st[i])+1;
}
work();
if(f[n]>1e18){
printf("Too hard to arrange\n");
}
else{
printf("%lld\n",(ll)f[n]),top=0;
for(int tp=n;tp;tp=pre[tp])stk[++top]=tp;
stk[++top]=0;
for(int i=top;i>1;i--){
for(int j=stk[i]+1;j<stk[i-1];j++)
printf("%s ",st[j]);
printf("%s\n",st[stk[i-1]]);
}
}
printf("--------------------\n");
}
return 0;
}
Yet Another Minimization Problem
将序列 \(a\) 分成 \(m\) 段,每一段 \(\lbrack l,r\rbrack\) 的代价是其中相同元素的对数,即 \(\sum_{l\le x<y\le r}\lbrack a_x=a_y\rbrack\).
试最小化总代价。
\(2\le n\le 10^5\),\(2\le m\le \min(n,20)\),\(1\le a_i\le n\).
记 \(f(i,k)\) 为前 \(i\) 个划分成 \(k\) 段的最小代价。
证明 \(w\) 满足四边形不等式:
怎么用比较优秀的复杂度求出 \(w\) 函数?可以像莫队一样左右移动指针做。
但是使用单调队列无法很好地保证指针移动的次数。这个时候就有必要用到分治优化了。
即 \(\text{dp}(l,r,bl,br)\) 表示已经确定 \(f\lbrack l\rbrack\) ~ \(f\lbrack r\rbrack\) 的最优决策点在 \(\lbrack bl,br\rbrack\),找到当前 \(mid\) 的最优决策 \(bm\),然后两边继续递归,也就是 \(\text{dp}(l,mid-1,bl,bm)\) 和 \(\text{dp}(mid+1,r,bm,br)\) .
由于决策单调性它的时间复杂度是 \(O(n\log n)\) 的,不太会证。
时间复杂度 \(O(kn\log n)\).
#include<bits/stdc++.h>
#define ll long long
#define N 100010
#define M 25
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,m,k,a[N];
int L,R,cnt[N];
ll sum,f[N][M];
int h,t,q[N];
void add(int x){
sum+=(cnt[x]++);
}
void del(int x){
sum-=(--cnt[x]);
}
ll w(int l,int r){
while(L>l)add(a[--L]);
while(R<r)add(a[++R]);
while(L<l)del(a[L++]);
while(R>r)del(a[R--]);
return sum;
}
void dp(int l,int r,int bl,int br){
if(l>r)return;
int mid=(l+r)>>1,bm=bl;
ll minn=f[bm][k-1]+w(bm+1,mid);
for(int i=bl;i<=min(br,mid-1);i++){
if(minn>f[i][k-1]+w(i+1,mid))
bm=i,minn=f[i][k-1]+w(i+1,mid);
}
f[mid][k]=minn;
dp(l,mid-1,bl,bm),dp(mid+1,r,bm,br);
}
int main(){
n=read(),m=read();
L=1,R=n;
for(int i=1;i<=n;i++){
a[i]=read(),sum+=(cnt[a[i]]++);
f[i][1]=sum;
}
for(k=2;k<=m;k++)dp(1,n,1,n);
printf("%lld\n",f[n][m]);
return 0;
}