动态规划

动态规划基础

动态规划问题的特征

  1. 子问题重复
  2. 最优子结构,无后效性(可以通过扩充状态满足这个特征)

LIS\text{LIS}

关于导弹拦截的介绍在这篇笔记里已经有了。

神级技巧!注意lower_bound(begin,end,x)upper_bound(begin,end,x)返回的都是迭代器!这么好的东西,我们怎么能不利用?

可以用*lower_bound(begin,end,x)=x将数组中第一个大于等于xx的数直接更改为xx

LCS\text{LCS}

此处LCS\text{LCS}指的是最长公共子序列(Longest Common Subsequence)。

如果是两个字符串的LCS\text{LCS},那么只能通过暴力的O(n2) dp\text{O}(n^{2})\text{ dp}解决。

如果是两个排列的LCS\text{LCS},可以转化为LIS\text{LIS}从而在O(nlogn)\text{O}(n\log n)的复杂度内解决。

考虑第二个序列中的每个数bib_{i},建立一个新序列ccci=pos(bi)=lower_bound(a,a+n,bi)ac_{i} = \text{pos} (b_{i})=\text{lower}\_\text{bound}( a , a+n , b_{i} ) - a
则序列cc的一个上升子序列ci1,ci2,,cimc_{i_1},c_{i_2},\cdots ,c_{i_m}就代表aci1a_{c_{i_1}}对应bi1b_{i_1}等,是一种公共子序列的对应。那么LIS\text{LIS}就代表着LCS\text{LCS}

如果对序列的限制改成:每个字符的出现次数为O(1)\text{O}(1),又怎么解呢?方法是:把一个字符拆成若干个字符来解!

对于某一个字符,如果它在第一个序列中出现多次,就把它拆分为多次出现位置的倒序。如序列a=(1,2,3,1,3,2,4)a=(1,2,3,1,3,2,4),序列b=(2,1,1,2,3,1,2)b=(2,1,1,2,3,1,2),则可以把序列bb拆为c=(6,2, 4,1, 4,1, 6,2, 5,3, 4,1, 6,2)c=(6,2,\text{ }4,1,\text{ }4,1,\text{ }6,2,\text{ }5,3,\text{ }4,1,\text{ }6,2),如22aa中出现的位置为2,62,6,就拆成6,26,2。这样做的好处是,如果有cc中的一个上升子序列,那么它同样对应着一个公共子序列;倒序保证bb中的同一个位置的数字不会对应aa中的多个数字,“上升”(而非“不降”)保证aa中的同一个位置的数字不会对应bb中的多个数字。

公共子序列

我也不记得怎么解了?

树形dp\text{dp}

聪聪可可

对于每一条路径,在它的起点和终点的LCA\text{LCA}处统计数量。

定义f[i][j](i[1,n],j{0,1,2})f[i][j](i\in [1,n],j\in \{0,1,2\})表示以ii为根的子树中,到ii的距离dis\text{dis}同余于jj的点的个数。在dfs\text{dfs}到某一个点时,初始化f[x][0]=1,f[x][1]=f[x][2]=0f[x][0]=1,f[x][1]=f[x][2]=0。每搜完一个子节点,就给答案累加上f[ch][0]×f[x][0]+f[ch][1]×f[x][2]+f[ch][2]×f[x][1]f[\text{ch}][0]\times f[x][0]+f[\text{ch}][1]\times f[x][2]+f[\text{ch}][2]\times f[x][1],再把f[ch]f[\text{ch}]的结果累加到f[x]f[x]上(加的顺序要注意,防止重复计数)。

Tree

点分治。具体做法考虑中。

Black Nodes in Subgraphs

中文题面见这里

到Vjudge提交

首先考虑预处理一些信息。

先证明一个性质:对于每一个ss,如果可行的最小bbb1b_1,可行的最大bbb2b_2,那么对于任意kN,k[b1,b2]k\in \mathbb{N},k\in [b_1,b_2](s,k)(s,k)都是可行的。

证明方法:若b1=b2b_1=b_2,显然成立。

否则从最大解中删除一个黑点,在周围寻找一个白点,如果寻找不到,就寻找一个黑点。重复上述过程,不可能一直找不到白点,因为一棵树是连通的,又有b1<b2b_1<b_2,因此只要向最小解方向靠拢,就可以找到白点。找到白点后用白点替换删除了的黑点,解的大小即减小11。由于解可以不断地减小直到达到最小答案,因此证明完毕。

证明了这个结论,我们也就只需求出,对于每一个ss,可行的bb的最大值和最小值分别是多少。而最大值和最小值是对称的,即最大值是黑点最多,最小值是白点最多。因此我们只需要考虑最大值的做法即可。

定义r[i]r[i]ii个点的连通块中黑点的最大数目,定义f[i][j]f[i][j]为在以ii为根的子树中,包含ii的共有jj个点的连通块中黑点的最大数目。r[j]=max(f[x][j])r[j]=\max(f[x][j])

对树进行dfs\text{dfs},对于某一棵子树的根节点xx,先初始化f[x][1]=color[x],size(x)=1f[x][1]=\text{color[x]},\text{size}(x)=1。接着对子节点ii进行搜索dfs(i,x),得到f[i][j]的所有结果(1jsize(i)1\le j\le \text{size}(i))。

为什么连通块强制包含ii呢?因为有了根节点,才能保证是连通的呀。

接着做一遍背包:

1
2
3
4
5
6
for (int i=1;i<=size[x];i++) //在加入这棵子树前的图中选i个点,一定选根
for (int j=0;j<=size[child];j++) //在子树中选j个点
tempmax[i+j]=max(tempmax[i+j],f[x][i]+f[child][j]); //背包
size[x]+=size[child]; //将子树大小累加
for (int i=1;i<=size[x];i++)
f[x][i]=tempmax[i]; //将tempmax数组中的数据转移回f数组

上述代码中使用tempmax数组的目的是在记录答案的同时不改变f数组中的数据。

遍历完xx的所有子节点后,我们把f[x][j]f[x][j]的答案更新到r[j]r[j]中。

最小值同理,再做一遍即可。

背包

Median Sum

这是ta\text{ta}出的测试题,不算太难。

使用大神器bitset\text{bitset}。开一个比总和大的bitset\text{bitset},初始化bs[0]=1\text{bs}[0]=1,对于每一个数,将bs=bs<<a[i]\text{bs}\mid =\text{bs}<<a[i],意为从前可行的每一个和,加上a[i]a[i]也是可行的。计算结果时从sum2\frac{\text{sum}}{2}开始往上找即可。

简单困难题

bzoj\text{bzoj}权限题,kule。

继续动用大神器。由于是异或,因此同一个和出现两次和没有出现是一样的,出现奇数次相当于出现11次。因此只需保存每一个和出现次数mod2\mod{2}的值。初始化和处理过程和上题相同,最后计算答案时扫描bitset\text{bitset},把为11的索引的值异或起来即得答案。

No Need

题意:对于集合AA中的某一个数xx,若BA,x∉B,yBy<k\exists B \subset A, x \not \in B,\sum_{y\in B}y<k,且yB{x}k\sum_{y\in B\cup \left \{ x \right \}}\ge k,则称xx是必要的。求集合中不必要的数的个数。

注意到A\varnothing \subset A,而对于xkx\ge k,令B=B=\varnothing,则yBy=0<k,yB{x}=xk\sum_{y\in B}y=0<k, \sum_{y\in B\cup \left \{ x \right \}}=x\ge k,知xx是必要的。因此,不必要的数必小于kk

我们还可以证明单调性:若aba\le b,且aa是必要的,那么bb也是必要的。

证明是,由于aa是必要的,知存在满足上述条件的集合BB
b∉Bb\not \in B,则yB{b}yB{a}k\sum_{y\in B\cup \left \{ b \right \}}\ge \sum_{y\in B\cup \left \{ a \right \}} \ge k
bBb \in B,则将BB中的bb换成aa得到集合CC,由aba\le byCyyBy<k\sum_{y\in C}y\le \sum_{y\in B}y<k,知CC满足题设条件。而yC{b}=yB{a}k\sum_{y\in C\cup \left \{ b \right \}}= \sum_{y\in B\cup \left \{ a \right \}} \ge k,证毕。

有了上下界和单调性,我们就可以进行二分了。注意,下面这段代码的真实时间复杂度是O(n2logn)\text{O}(n^2 \log n),因为bitset\text{bitset}的移位操作在复杂度上是O(n)\text{O}(n)的,只是常数很小而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <cstdio>
#include <algorithm>
#include <bitset>
#define MAXN 5005
#define MAXBS 5005
using namespace std;
bitset<MAXBS> bs;
int a[MAXN];
int main(void){
int n,k;
int ll,rr;
scanf("%d%d",&n,&k);
for (int i=0;i<n;i++){
scanf("%d",a+i);
}
sort(a,a+n);
n=lower_bound(a,a+n,k)-a;
//(ll,rr]
ll=-1,rr=n;
while (rr-ll>1){
int mid=(ll+rr+1)>>1;
int necessary=0;
bs.reset(); // bs=0
bs.set(0); // bs[0]=1;
for (int i=0;i<n;i++)
if (i!=mid)
bs|=(bs<<a[i]);
bs>>=(k-a[mid]);
for (int i=0;i<a[mid];i++){
if (bs.test(i)){
necessary=1;
break;
}
}
if (necessary)
rr=mid;
else
ll=mid;
}
printf("%d\n",ll+1);
return 0;
}

当然,除了二分,我们还有O(n2)\text{O}(n^2)的更优的解法。

二分法为什么慢呢?因为对于每一个mid\text{mid},我们都要计算一遍除了amida_{\text{mid}}以外的元素的组合能达到的数值。下面的方法可以避免重复计算。

我们还需要一个结论。设s=i=1kais=\sum_{i=1}^{k}a_{i},如果BA,{a1,a2,,ak}B=,yBy<k\exists B \subset A, \left\{ a_{1},a_{2},\cdots,a_{k} \right \} \cap B =\varnothing, \sum_{y\in B}y<k,且s+yBks+\sum_{y\in B}\ge k,那么aka_{k}是必要的。

证明:假设aka_k不是必要的,那么根据单调性,a1,a2,,aka_1,a_2,\cdots,a_k都不是必要的。

根据题目条件,将a1,a2,,aka_1,a_2,\cdots,a_k按顺序加入到BB中,则必存在1jk1\le j\le k,使加入a1,a2,,ak1a_1,a_2,\cdots,a_{k-1}时,和小于kk;加入aja_j时,和大于等于kk。因此aja_j是必要的,与上述假设矛盾。

故假设不成立,aka_{k}是必要的。

于是,我们可以根据ak+1,ak+2,,ana_{k+1},a_{k+2},\cdots,a_{n}的组合能达到的范围来确定aka_k的必要性,这样就避免了bitset\text{bitset}的重复计算,时间复杂度降到O(n2)\text{O}(n^2)

数位dp\text{dp}

Counting Digits / 梦中的统计 / 数字计数

先计算1m<n1\le m <n的整数中出现数字xx出现的次数。

依次考虑每一位,考虑个位时令t=1t=1,考虑十位时令t=10t=10,考虑百位时令t=100t=100……

那么在T=10tT=10t的一个完整周期内,这一位上数字xx共出现了tt次。

考虑不完整的那一个周期,如果这一位上的数nt%10>x\lfloor\frac{n}{t}\rfloor \% 10 > x,那么出现了完整的tt次;如果nt%10=x\lfloor\frac{n}{t}\rfloor \% 10 = x,那么出现了n%tn \% t次;如果nt%10<x\lfloor\frac{n}{t}\rfloor \% 10 < x,那么没有出现。

对于Counting Digits,暴力对每个数求解即可。优化是如果一个xx不是解,且函数值为yy,那么只比xx大一点的数也不会是解,因此xx可以增加一个大一点的数。

另外,Counting Digits可以到这里提交。

对于梦中的统计,求出端点相减即可。

“数字计数”是只有一组数据的版本。双倍经验

状态压缩

Sphere Packing

相当于求哈密顿路。状压。

状态转移的必备技巧(又名FMT\text{FMT},Fans MeeTing Fast Mobius Transform)

已知数组a[0...2k1]a[0...2^k-1]。设b[i]=j&i=ja[j]b[i]=\sum_{j\& i=j}a[j](可以认为是AA的所有子集的权值之和)。请在O(k2k)\text{O}(k2^{k})的时间内求出bb数组的值。

暴力法:对每一个i[0...2k1]i\in [0...2^k-1],枚举j[0...2k1]j\in [0...2^k-1],如果满足条件,则求和。O(4k)\text{O}(4^k)

子集枚举法:对每一个集合AA,枚举AA的所有子集,进行求和。

如何枚举子集?

1
2
3
4
5
6
int i; //枚举A(表示为i)的子集
for (int j=i;;j=(j-1)&i){
//进行操作
if (!j)
break;
}

时间复杂度O(3k)\text{O}(3^{k})

正解:进行递推。

1
2
3
4
5
6
7
8
int len=1<<k;
for (int i=0;i<k;i++){
int tpow=1<<i;
for (int j=0;j<len;j++){
if (j&tpow)
f[j]+=f[j^tpow];
}
}

如何理解上面这段代码呢?

请看拙作第二题

补充:请注意FMT\text{FMT}的应用范围!参考洛谷1111月月赛 咕咕咕

Upd: 这个东西又叫做 SoS (Sum over Subsets) dp

Or Plus Max

转化:ijki\mid j\le k这个条件很难利用,我们可以求一个数组f[k]=max(Ai+Aj)  (ij=k)f[k]=\max(A_i+A_j)\ \ (i\mid j=k),求f[k]f[k]的前缀最大值g[k]=max(f[i])  (ik)g[k]=\max(f[i])\ \ (i\le k),则g[k]g[k]即为答案数组。

这么计算同样有难度。我们再进行一次转化:令h[k]=max(Ai+Aj)(ik,jj,ij)h[k]=\max(A_i+A_j)(i\subseteq k,j\subseteq j,i\neq j),则我们直接用FMT\text{FMT}求出kk的子集中AA的最大值和次大值即可。

期望dp\text{dp}

分手是住院祝愿

首先考虑最优策略。假设当前亮着的编号最大的灯是xx,则关掉它的最优策略是按下开关xx把它关上,同时xx的因数的灯的状态会改变。我们接着寻找下一个亮着的灯,继续如此操作。现在我们就能算出,如果一直按照最优策略,需要操作的次数。这样就能拿到k=nk=n5050分了。

现在我们考虑随机情况。设状态kk为“此时按照最优策略,还需操作kk次”,f[k]f[k]表示从状态kk转移到状态k1k-1所需操作次数的期望。

在状态kk,如果我们按了正确的开关(即kk个正确开关中的一个),那么状态就转移成了k1k-1;如果我们按了其余的nkn-k个错误的开关,我们就必须再按一次这个错误的开关,因此状态被转移到了k+1k+1。而状态k+1k+1要转移到状态k1k-1,首先必须回到状态kk。于是得到f[k]=kn×1+nkn×(1+f[k+1]+f[k])f[k]=\frac{k}{n}\times 1+\frac{n-k}{n}\times (1+f[k+1]+f[k])。由此式知f[n]=1,f[k]=(nk)f[k+1]+nkf[n]=1,f[k]=\frac{(n-k)f[k+1]+n}{k}。可以从f[n]f[n]开始,递推得到所有ff的值。

因此若设最优策略的步数为tt,则答案即为

i=k+1tf[i]+i=1k1\sum^{t}_{i=k+1}f[i]+\sum^{k}_{i=1}1

有分数怎么办?最后不是求得不就是ans×n! %100003\text{ans}\times n! \text{ }\% 100003的值嘛,因此计算过程中遇到除法就换成乘以逆元就好了。

WJMZBMR打osu! / Easy

期望有些很神奇的性质!

期望的线性性质 Ex(k1X+k2Y)=k1Ex(X)+k2Ex(Y)\text{Ex}(k_1X+k_2Y)=k_1\text{Ex}(X)+k_2\text{Ex}(Y) (见维基百科 Expected value

因此,期望可以乱加(逃)。只要是线性的,我们就方便处理。

考虑一段连续的o,第kk个位置和第k+1k+1个位置的得分满足(k+1)2k2=2k+1(k+1)^2-k^2=2k+1是线性的,可以处理。因此我们得出,如果一个位置是o,那么这个位置的得分的期望可以由上一个位置的得分期望计算出。

f[i]f[i]表示到第ii步为止得分的期望,g[i]g[i]表示在第ii步末尾连续o的个数的期望。设u[i]=f[i]f[i1]u[i]=f[i]-f[i-1]

现在对第ii步的状况进行讨论。

如果第ii步是x,那么u[i]=0,g[i]=0u[i]=0,g[i]=0
如果第ii步是o,那么根据上面的讨论,u[i]=2g[i1]+1,g[i]=g[i1]+1u[i]=2g[i-1]+1,g[i]=g[i-1]+1
如果第ii步是?,那么u[i]=(0+2g[i1]+1)÷2=g[i1]+0.5,g[i]=g[i1]+0.5u[i]=(0+2g[i-1]+1)\div 2=g[i-1]+0.5,g[i]=g[i-1]+0.5

于是只需要再使用f[i]=f[i1]+u[i]f[i]=f[i-1]+u[i]计算即可。可以使用滚动数组节省空间。最后的答案即为f[n]f[n]

按位或

这是篇好博客,如果看不懂我自己写的就看这里的吧。

暴力法

f[i]f[i]表示从数ii2n12^n-1所需操作次数的期望,则f[2n1]=0f[2^n-1]=0f[x]=1+i=02n1f[xi]×p[i]f[x]=1+\sum_{i=0}^{2^{n}-1}f[x \mid i]\times p[i]。于是最暴力的方法就有了:对于每一个xx,枚举i⊈xi\not\subseteq x,将p[i]×f[xi]p[i]\times f[x\mid i]进行累加后加上11,再除以f[x]f[x]项的系数1ixp[i]1-\sum_{i\subseteq x}p[i],即得f[x]f[x]。最后输出f[0]f[0]

正解

看题解吧……

歌唱王国

利用KMP\text{KMP}next\text{next}数组。

奇妙的解法

阿狸和桃子的游戏

如果只有点权,就很简单了。

加上边权了如何呢?

把边权平分到两个端点上,如果这条边被两个人分别选择,那么差值为00;否则这两半会累加成一个完整的边权。

tql

星之器

我们可以发现,无论操作方法如何,从初状态转移到末状态释放的能量是相等的。考虑物理中势能的概念,我们定义一个点(x,y)(x,y)的单位势能为Es=x2+y22E_{s}=\frac{x^2+y^2}{2},一个点的势能为Ep=cnt(star)EsE_{p}=\text{cnt}(\text{star})E_s,即星星数目乘以单位势能。定义一个状态的势能为所有点的势能之和。那么从初状态到末状态的能量释放数即为势能差。

Friends

答案就是32n32^n。可以使用long double\text{long double}直接输出。

这个答案可以从样例推得。因为每一种语言都是独立的,设语言种数为nn,则答案一定是ana^{n}。代入样例知a=32a=32

一道防AK好题

从结束标志0,0,00,0,0倒推。

评论