本篇题解可能含有大量窝理解缓慢写出来的废话
下面主要是讲推出式子的思路
如果大佬们觉得一些地方很显然的话还是选择性跳着读吧(
解题方法看代码就看得出来的
这种类型的不好直接转移,
如果只知道区间的大概信息也不知道有哪些数被选(状压啥的在分析之前就被数据范围
x
\mathsf{x}
x掉了
所以得另外开一个数组预处理一些信息:比如
[
i
,
j
]
[i,j]
[i,j]里面的数值种类数
d
i
f
f
[
i
]
[
j
]
\mathfrak{diff[i][j]}
diff[i][j]。
方程
f
i
=
m
i
n
{
f
j
+
d
i
f
f
j
∼
i
2
}
\mathfrak{f_{i}=min\{f_j+diff_{j\thicksim i}^2\}}
fi=min{fj+diffj∼i2}
然而这题不给
Θ
(
n
2
)
\mathfrak{\Theta(n^2)}
Θ(n2)。那么
d
i
f
f
[
i
]
[
j
]
\mathfrak{diff[i][j]}
diff[i][j]就别想了
d
p
\mathfrak{dp}
dp暂且到此为止。
——换一种思路。考虑极端情况来入手。
比如讲,如果一个也不分,
a
n
s
=
n
2
\mathfrak{ans=n^2}
ans=n2。
中间的略过,先考虑更直接简单的情况:如果每一个都分出来,
a
n
s
\mathfrak{ans}
ans?
=
n
\mathfrak{=n}
=n。
那么,最优解一定有:它的
a
n
s
≤
n
\mathfrak{ans}\le n
ans≤n。
放在长度
l
e
n
\mathfrak{len}
len的小区间上有
a
n
s
′
≤
l
e
n
\mathfrak{ans'\le len}
ans′≤len,于是一段区间里面不同种类数的种数不能超过
l
e
n
\mathfrak{\sqrt{len}}
len。
所以怎么利用?
这里差点歪出一颗主席树来
好像复杂度是
Θ
(
n
n
)
\mathfrak{\Theta(n\sqrt{n})}
Θ(nn)了吧?
其中
n
\mathfrak{\sqrt{n}}
n的产生方式有可能会是要枚举
n
\mathfrak{\sqrt{n}}
n
…?这么想呢。能够枚举到
n
\mathfrak{\sqrt{n}}
n的也就是种数了?
回到
d
p
\mathfrak{dp}
dp考虑。现在空间上地、有两维。
第一维多半是位置。(可以滚掉)
第二维也一般和上一个集合有关。
放到这题里面,如果第二维只表示到现在有几个集合那好像没有什么用。
需要表示的,有可能是
d
i
f
f
\mathfrak{diff}
diff;不过为了转移这个一定不能放进去,
在二维里面的应该是能确定更多关于之前集合信息的:比如上一个集合的末端。
至于
d
i
f
f
\mathfrak{diff}
diff可以间接来决定,例如说,决定“上一个集合”——
考虑时间上,两层循环?
第一层是位置、递增。记为
i
\mathfrak{i}
i。
既然说要用
d
i
f
f
\mathfrak{diff}
diff来间接决定空间二维,那时间的二维就是它了。
j
\mathfrak{j}
j。
可以记
p
o
s
j
\mathfrak{pos_j}
posj 表示当前位置
i
\mathfrak{i}
i,往前面的一个从其右端点右侧到
i
\mathfrak{i}
i的
d
i
f
f
≤
j
\mathfrak{diff\le j}
diff≤j的区间。
然后转移就是选进(这个区间右端点右侧 到
i
\mathfrak{i}
i)的区间。
当然选进来的区间越长越好,反正它能被选就是它的
d
i
f
f
\mathfrak{diff}
diff符合要求
≤
j
\le\mathfrak{j}
≤j。
啊对了,到这里不用求
d
i
f
f
\mathfrak{diff}
diff,而是要求最长
d
i
f
f
=
j
\mathfrak{diff=j}
diff=j
实现显然可以对于一个数处理跟他相同的数(位置上前驱/后继),
对于
p
o
s
x
\mathfrak{pos_x}
posx同时处理
c
n
t
x
\mathfrak{cnt_x}
cntx表示
a
p
o
s
x
\mathfrak{a_{pos_x}}
aposx出现几次了。
复杂度: Θ ( n n ) \mathfrak{\Theta(n\sqrt{n})} Θ(nn)。
f
i
=
m
i
n
{
f
p
o
s
j
+
j
2
}
\mathfrak{f_i=min\{f_{pos_j}+j^2\}}
fi=min{fposj+j2}
维护一下
n
\mathfrak{\sqrt{n}}
n个
p
o
s
j
\mathfrak{pos_j}
posj。
我怎么分析了这么长(鶸爆了
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<queue>
#include<cstring>
#include<cctype>
using namespace std;
int N,M,L,A;
int P[40005]={};
int F[40005]={};
int head[40005]={};
int nxt[40005]={};
int pre[40005]={};
int cnt[40005]={};
int diff=0;
int main()
{
scanf("%d%d",&N,&M); L=sqrt(N);
for(int i=1;i<=N;++i)
{
nxt[i]=N+1;
scanf("%d",&A);
pre[i]=head[A];
nxt[head[A]]=i;
head[A]=i;
F[i]=0x3f3f3f3f;
}
nxt[N+1]=N+1;
for(int t,i=1;i<=N;++i)
{
if(!pre[i])++diff;
t=min(diff,L); //*
for(int j=1;j<=t;++j)
{
if(!cnt[j])cnt[j]=diff; else if(pre[i]<=P[j])++cnt[j];
while(cnt[j]>j)if(nxt[++P[j]]>i)--cnt[j];
F[i]=min(F[i],F[P[j]]+j*j);
}
}
printf("%d",F[N]);
return 0;
}
Summary
d
p
\mathfrak{dp}
dp的过程实际上(大约)就是在一个状态的
S
−
T
  
D
A
G
\mathcal{S-T\;DAG}
S−TDAG上面逐层(可能高维)跑答案
因此
d
p
\mathfrak{dp}
dp要满足无后效性和最优化子结构性质。
已经最终决定的子问题,它和推出它的子问题里面只有它会对后面的问题有贡献。
子问题最优解可以得到原问题最优解。(实际上就是 f \mathfrak{f} f是有效的,能够求解问题)
同时,
d p \mathfrak{dp} dp是由搜索演变来的,而相比搜索, d p \mathfrak{dp} dp牺牲了空间复杂度存状态来换取时间复杂度的降低。
d p \mathfrak{dp} dp存储状态,就是为了避免像朴素递归穷举那样多次重复求解同一个问题
这一点上 d p \mathfrak{dp} dp或许也可以算作一种剪枝?
由此, d p \mathfrak{dp} dp很大的作用就是避免多次解决重复问题。
于是在题目的一层问题上,一般来说能分治就不 d p \mathfrak{dp} dp,能 d p \mathfrak{dp} dp就不分治。
因为能分治(不考虑那些奇奇怪怪的分治)就是子问题不带重复
子问题带重复就分治不了。
可以由以上三个性质来考虑问题是否能用
d
p
\mathfrak{dp}
dp解决。
(当然
d
p
\mathfrak{dp}
dp做多了、这种考虑可能会很自然)
有这么一种说法,“贪心是第一数学归纳法,
d
p
\mathfrak{dp}
dp是第二数学归纳法”
贪心,
f
i
→
f
i
+
1
\mathfrak{f_i\to f_{i+1}}
fi→fi+1;
d
p
\mathfrak{dp}
dp,
f
1
∼
i
→
f
i
+
1
\mathfrak{f_{1\thicksim i}\to f_{i+1}}
f1∼i→fi+1。
寻找朴素
d
p
\mathfrak{dp}
dp方程的方法就是尽量满足三个性质。
优化的方法?
空间优化基本上就那么样了。
时间上,
优化/设计方法
- 利用数据结构优化(比如转移方程里面有区间x之类的操作)或者数学变换
- 利用题目本身的性质(单调性、区间可加之类)
- 对于特定形式的方程,比如四边形不等式,斜率优化,单调队列
- 将状态延迟处理,比如把 f i \mathfrak{f_i} fi沿用到 f i + j \mathfrak{f_{i+j}} fi+j,有时可以优化掉一维;类似的前缀和
- 某一类问题(插头、轮廓线、计数、状压、期望、概率等)有比较通用的思路
- 分治偶尔可以跟 d p \mathfrak{dp} dp一起用, l o g \mathfrak{log} log掉一个 n \mathfrak{n} n
- 一类带修改的动态 d p \mathfrak{dp} dp问题(就不在这里详谈吧)
- 利用自动机等构造 d f a \mathfrak{dfa} dfa转移图并且可能可以用于 d p \mathfrak{dp} dp上;矩阵加速
- 推结论。
甚至可以推倒dp - 逆向思维
- 在某些部分可以尝试结合其他算法,比如 d p \mathfrak{dp} dp+图论啥的