如果一个 dp 数组满足四边形不等式 f [ a , c ] + f [ b , d ] < = f [ a , d ] + f [ b , c ] f[a,c]+f[b,d]<=f[a,d]+f[b,c] f[a,c]+f[b,d]<=f[a,d]+f[b,c] ( a < b ≤ c < d a<b\leq c<d a<b≤c<d,交叉小于包含),那么它满足决策单调性 s [ i , j ] < = s [ i + 1 , j ] < = s [ i + 1 , j + 1 ] s[i,j]<=s[i+1,j]<=s[i+1,j+1] s[i,j]<=s[i+1,j]<=s[i+1,j+1]。(证明见百度百科)。
如果 dp 方程满足 f [ i ] [ j ] = m i n { f [ i ] [ k ] + f [ k + 1 ] [ j ] } + c o s t ( j , i ) f[i][j]=min\{f[i][k]+f[k+1][j]\}+cost(j,i) f[i][j]=min{f[i][k]+f[k+1][j]}+cost(j,i), w w w 满足四边形不等式,且满足 w [ a , d ] > = w [ b , c ] w[a,d]>=w[b,c] w[a,d]>=w[b,c], a ≤ b ≤ c ≤ d a\leq b\leq c\leq d a≤b≤c≤d。那么 f f f 也满足决策单调性。
如果 f [ i ] = min { f [ j ] + c o s t ( j , i ) } f[i]=\min\{f[j]+cost(j,i)\} f[i]=min{f[j]+cost(j,i)}, c o s t cost cost 满足四边形不等式(或者说代价为凸),也是有决策单调性的。
剩下的就是yy。
当 dp 数组是从上一层转移过来的时候,可以用分治求解决策单调性问题。分治的一个好处是不需要快速计算任意 c o s t cost cost 函数,只需要快速转移相邻的 c o s t cost cost 即可。
做法是 s o l v e ( l , r , x , y ) solve(l,r,x,y) solve(l,r,x,y) 表示我们要转移 l , r l,r l,r 中的 dp 值,决策点在 x , y x,y x,y 范围里。每次我们暴力计算出 m i d mid mid 的最优决策点 p p p,递归计算 s o l v e ( l , m i d − 1 , x , p ) solve(l,mid-1,x,p) solve(l,mid−1,x,p), s o l v e ( m i d + 1 , r , p , y ) solve(mid+1,r,p,y) solve(mid+1,r,p,y)。
关于复杂度:对于决策点的枚举,每一层只会遍历数组一遍。对于价值函数的计算,从这一层的 [ x , y ] [x,y] [x,y] 到下一层的 [ x , p ] [x,p] [x,p],变化是区间长度级别的。枚举决策点的时候价值函数的计算是 O ( 1 ) O(1) O(1) 的。因此复杂度为 O ( n log n ) O(n\log n) O(nlogn)。
CF868F
给定一个序列,要把它分成k个子序列。每个子序列的费用是其中相同元素的对数。求所有子序列的费用之和的最小值。
n ≤ 100000 , k ≤ 20 n\leq 100000,k\leq 20 n≤100000,k≤20
分治写法的模板题。
#include
#define ll long long
using namespace std;
int tong[100010],l=1,r,now,n,k,a[100010];
ll f[21][100010];
ll sum=0;
inline int read()
{
char c=getchar();int x=0,flag=1;
while(!isdigit(c)){if(c=='-') flag=-1;c=getchar();}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*flag;
}
void add(int x) {sum+=tong[a[x]]++;}
void del(int x) {sum-=--tong[a[x]];}
void check(int L,int R)
{
while(rL) add(--l);
while(r>R) del(r--);
while(lr) return;
int mid=l+r>>1;
int p=min(mid,y),k;
for(int i=p;i>=x;i--)
{
check(i+1,mid);
if(f[now-1][i]+sum
可以解决同层 dp 转移的问题,缺点是需要快速计算 c o s t cost cost。
每个决策点影响到的一定是一个区间,因此用单调栈维护当前每个点的最优决策点。单调栈里是若干个区间 [ l , r , p ] [l,r,p] [l,r,p] 表示 l , r l,r l,r这个区间的最优决策点是 p p p。每次新加入一个决策点,我们它能否完全替代栈顶,是的话就弹栈,不是的话在这个区间里 erf,然后分裂成两个区间。
诗人小G
给定一些字符串,划分成若组,每组的代价是字符串总长-给定常数的 p p p 次方。求最小代价和。
单调栈写法的模板题。
#include
#define ld long double
#define ll long long
using namespace std;
const int N=100010;
struct poss{
int l,r;
}pos[N];
ld f[N];
int n,L,p,pre[N],q[N],sum[N];
char s[N][33];
int read()
{
int x=0;char c=getchar(),flag='+';
while(!isdigit(c)) flag=c,c=getchar();
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return flag=='-'?-x:x;
}
ld qpow(int a,int b)
{
ld ans=1,base=a;
while(b)
{
if(b&1) ans=ans*base;
base=base*base;
b>>=1;
}
return ans;
}
ld calc(int i,int x)
{
return f[i]+qpow(abs(sum[x]-sum[i]-1-L),p);
}
void print(int x)
{
if(pre[x]) print(pre[x]);
for(int i=pre[x]+1;i<=x;i++)
{
printf("%s",s[i]+1);
if(i!=x) putchar(' ');
}
putchar('\n');
}
int main()
{
int T=read();
while(T--)
{
n=read(),L=read(),p=read();
for(int i=1;i<=n;i++)
{
scanf("%s",s[i]+1);
sum[i]=sum[i-1]+strlen(s[i]+1)+1;
}
int h=1,t=1;
q[h]=0,pos[0].l=1,pos[0].r=n;
for(int i=1;i<=n;i++)
{
while(pos[q[h]].r>1;
if(calc(i,mid)<=calc(q[t],mid)) ans=mid,r=mid-1;
else l=mid+1;
}
if(ans<=n)
{
pos[q[t]].r=ans-1;
q[++t]=i;
pos[i].l=ans;
pos[i].r=n;
}
}
if(f[n]>1e18) puts("Too hard to arrange");
else printf("%lld\n",(long long)f[n]),print(n);
puts("--------------------");
}
return 0;
}