清北学堂2018DP&图论精讲班 DP部分学习笔记
Day 1
上午
讲的挺基础的……不过还是有些地方不太明白
例1
给定一个数n,求将n划分成若干个正整数的方案数。
例2
数字三角形
例7
最长不下降子序列
以上太过于基础,不做深入讨论
例3
给定一个数n,求将n划分成若干个正整数的方案数。
题解:
- 定义状态
\(dp[i][j]\)表示用不超过\(j\)的数来组成\(i\) - 状态转移
\(i < j \;\;\; dp[i][j]=dp[i][i]\)
\(i = j \;\;\; dp[i][j]=dp[i][j-1]+1\)
\(i > j \;\;\; dp[i][j]=dp[i-j][j-1]+dp[i][j-1]\)
例4
一个人站在楼梯的第一级上,每次他可以向上走1~m级。有某些级楼梯是坏的,不能走上去。而且连续走了\(k\)次\(m\)级之后你接下来的一步只能走1级。问走到第N级的方案数。
题解:
- 定义状态
\(dp[i][j]\)在第\(i\)级台阶上,连续走了\(j\)次\(m\)级 - 状态转移
\(\sum_{j=1}^{m-1}dp[i+j][0]+= \sum_{l=0}^{k-1}dp[i][l]\) (我自己按老师的意思写的方程我自己都看不懂……)
\(dp[i+1][0]+=dp[i][k]\)
\(dp[i+m][l+1]+=f[i][l]\;(l\neq k)\)
例5
Codeforces 467C George and Job
给定一个长度为n的序列,从序列中选出k个不重叠且连续的m个数,要求和最大。
\(1<=m\times k<=n<=5000\)
题解:
- 定义状态
\(sum[]\)为前缀和,\(dp[i][j]\)选了\(j\)段,以\(i\)为结尾 - 状态转移
\(dp[i][j]=max(dp[i-m][j-1]+sum[i]-sum[i-m],dp[i-1][j])\)
例6
当给定一个序列\(a[0],a[1],a[2],...,a[n-1]\)和一个整数\(K\)时,我们想找出有多少子序列里面的所有元素乘起来恰好等于\(K\)。
方案数对\(10^9+7\)取模。
\(n <= 1000,k <= 10^8\)
不会,全程懵逼
下午
考试,估计要爆零……
嗯,60分,还不错——至少比想象中的高
因为可能有版权原因,就不放题目和题解了
吐槽
- T1
只能说我太菜了,根本不会DP,爆搜+数据特判,40分滚粗 - T2
我会最短路,怎么才20分?好吧,那30%\(k=1\)的测试点我承认我删边删错了。题目是双向边,我也是按双向边存的,结果删的时候只删了一条边…… - T3
不会,讲了也不会 (`^′)ノ
Day 2
上午
上午先讲了昨天没讲完的几道题,好吧,我太菜了,一道也不会 QAQ
接Day1 例7
例8
给出一个整数数组A,你可以将任何一个数修改为任意一个正整数,最终使得整个数组是严格递增的且均为正整数。问最少需要修改几个数?
\(n < = 100000\)
题解:
这道题思路很妙。
- \(a[\;]\)表示原序列
首先,我们将每个数\(a[i]\)减去它们对应的下标\(i\),然后将\(< 0\)的\(a[i]\)删去。因为每一个数都要是正整数,所以如果\(a[i] < i\),那它肯定不符合要求。
然后我们再在更改后的序列上找最长不下降子序列。最后用n-最长不下降子序列的长度就OK了
例9
OpenJudge 6047 (找不到这道题 \(\rm{Orz}\))
有一块矩形大蛋糕,长和宽分别是整数\(w ,h\)。现要将其切成\(m\)块小蛋糕,每个小蛋糕都必须是矩形、且长和宽均为整数。切蛋糕时,每次切一块蛋糕,将其分成两个矩形蛋糕。请计算:最后得到的\(m\)块小蛋糕中,最大的那块蛋糕的面积下限。
\(w,h,m <= 20\)
题解:
- 定义状态
\(dp[i][j][k]\)表示长宽为\(i,j\)的蛋糕切\(k\)刀的答案 - 边界条件
\(dp[i][j][0]=i\times j\) - 状态转移
\(dp[i][j][k]=min(max(dp[i][o][p],dp[i][j-o][k-1-p],dp[o][j][p],dp[i-o][j][k-p-1]))\)
例10
有\(n+1\)个房间,一个人在1号房间。如果这是他第奇数次到当前房间(\(i\)号),那么他会去\(pi\; (pi { <= }i)\)号房间,否则他会去\(i+1\)号房间。不管他去了那个房间,他的移动次数+1。
到达n+1号房间停止移动。问这时他的移动次数。答案对\(1000000007\)取模。
\(n <= 1000\)
题解:
- 定义状态
\(dp[i][0]\)表示奇数次到达\(i\)号房间,\(dp[i][1]\)表示偶数次到达\(i\)号房间 - 状态转移
方程不如代码好表达(不想再写一个自己都看不懂的\(\Sigma\)了),所以我就把代码给搬上来了 qwq
//a[i]是原序列,dp数组如上所说
for(int i=1;i<=n;i++){
dp[i][0]=(dp[i-1][1]+dp[i-1][0]+1)%mod;
for(int j=a[i];j<i;j++)
dp[i][1]+=(dp[j][1]+1)%mod;
dp[i][1]++;
}
cout<<(dp[n][1]+dp[n][0])%mod;
至此,基础(?)的DP就讲完了
进入——区间DP
例1
在一行\(n\)个格子上进行游戏,每个格子有一个分数\(a[i]\)。你在\(1\)号格子,每次可以向前走\(1/2/3/4\)个格子,每种走法限制最多走\(b_1/b_2/b_3/b_4\)次。一次走法的分数是走过的格子的分数和。问走到n号格子的最大分数。
保证\(b_1+2\times b_2+3\times b_3+4\times b_4=n-1\)(恰好走完所有的次数)
\(n<=350,a[i]<=100,b_i<=40\)
题解:
- 定义状态
\(dp[i][j][k][l]\)表示各种类别的卡片分别还剩多少 - 状态转移
算了,本来想打方程的,懒了,丢代码吧,感受一下四维DP的魅力吧! 233
for(int i=0;i<=cards[1];i++)
for(int j=0;j<=cards[2];j++)
for(int k=0;k<=cards[3];k++)
for(int l=0;l<=cards[4];l++){
pos=1+i+j*2+k*3+l*4;
if(i) dp[i][j][k][l]=max(dp[i][j][k][l],dp[i-1][j][k][l]+mark[pos]);
if(j) dp[i][j][k][l]=max(dp[i][j][k][l],dp[i][j-1][k][l]+mark[pos]);
if(k) dp[i][j][k][l]=max(dp[i][j][k][l],dp[i][j][k-1][l]+mark[pos]);
if(l) dp[i][j][k][l]=max(dp[i][j][k][l],dp[i][j][k][l-1]+mark[pos]);
}
cout<<dp[cards[1]][cards[2]][cards[3]][cards[4]]+mark[1];
例2
有两个仅包含小写英文字母的字符串\(A\)和\(B\)。现在要从字符串\(A\)中取出\(k\)个互不重叠的非空子串,然后把这\(k\)个子串按照其在字符串\(A\)中出现的顺序依次连接起来得到一个新的字符串,请问有多少种方案可以使得这个新串与字符串\(B\)相等?注意:子串取出的位置不同也认为是不同的方案。输出方案数%1000000007
\(length(A) <= 1000,1 <= k <= length(B) <= 200\)
题解:
这道挺毒的,卡空间,必须用滚动数组优化
- 定义状态
\(dp[i][j][k][0/1]\)表示字符串\(A\)到\(i\),字符串\(B\)到\(j\),取出了\(k\)个字符串,第\(i\)个字符选不选 - 边界条件
dp[0][0][0][0]=dp[1][0][0][0]=1;
- 状态转移
以后有代码就直接丢代码了,懒了懒了
for (int i=1;i<=n;i++,pos^=1)
for(int j=1;j<=m;j++){
for(int o=1;o<=k;o++){
dp[pos][j][o][0]=dp[pos^1][j][o][1]%mod+dp[pos^1][j][o][0]%mod;
if(a[i]==b[j])
dp[pos][j][o][1]=dp[pos^1][j-1][o-1][0]%mod+dp[pos^1][j-1][o][1]%mod+dp[pos^1][j-1][o-1][1]%mod;
else dp[pos][j][o][1]=0;
}
}
cout<<(dp[n&1][m][k][0]+dp[n&1][m][k][1])%mod;
例3
N堆石子摆成一条线。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价。计算将N堆石子合并成一堆的最小代价。
\(n { <= } 100\)
题解:
这个题目要求最小代价,除了最小代价,Luogu还要求求出最大代价,不过实现的方法一模一样
很经典的一道题目,区间DP入门必刷题
首先要破环为链+前缀和处理
- 定义状态
\(dp[i][j]\)表示合并区间\([i,j]\)的最小代价 - 状态转移
for(int i=2*n-1;i>=1;i--)
for(int j=i+1;j<=i+n;j++){
dp2[i][j]=214748364;
for(int k=i;k<j;k++)
dp2[i][j]=min(dp2[i][j],dp2[i][k]+dp2[k+1][j]+sum[j]-sum[i-1]);
}
例4
设一个\(n\)个节点的二叉树\(tree\)的中序遍历为\((1,2,3,…,n)\),其中数字\(1,2,3,…,n\)为节点编号。每个节点都有一个分数(均为正整数),记第i个节点的分数为\(di\),\(tree\)及它的每个子树都有一个加分,任一棵子树\(subtree\)(也包含\(tree\))的加分计算方法如下:
\(subtree\)的左子树的加分\(\times subtree\) 的右子树的加分\(+subtree\)的根的分数。
若某个子树为空,规定其加分为\(1\),叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为\((1,2,3,…,n)\)且加分最高的二叉树\(tree\)。要求输出\(tree\)的最高加分
\(n {<=} 30\)
题解:
emmmm,第三次碰到这题了,到现在还记得当时爆零的屈辱
- 定义状态
\(dp[i][j]\)表示区间\([i,j]\)的最大得分 - 边界条件
for(int i=1;i<=n;i++){
dp[i][i]=num[i]; //num[ ]为原序列
dp[i][i-1]=1;
}
- 状态转移
for(int i=n;i>=1;i--)
for(int j=i+1;j<=n;j++)
for(int k=i;k<=j;k++)
if(dp[i][k-1]*dp[k+1][j]+num[k]>dp[i][j]){
dp[i][j]=dp[i][k-1]*dp[k+1][j]+num[k];
tree[i][j]=k; //用于输出
}
cout<<dp[1][n]<<endl;
- 输出
这道题输出也是个坑
void print(int l,int r){
if(l>r) return ;
if(l==r){
cout<<l<<" ";
return ;
}
cout<<tree[l][r]<<" ";
print(l,tree[l][r]-1);
print(tree[l][r]+1,r);
}
例5
折叠的定义如下:
一个字符串可以看成它自身的折叠。记作\(S = S\)
\(X(S)\)是\(X(X>1)\)个\(S\)连接在一起的串的折叠。记作\(X(S) = SSSS…S(X\)个\(S)\)。
如果\(A = A',B = B'\),则\(AB = A'B'\)例如,因为\(3(A) = AAA, 2(B) = BB\),所以\(3(A)C2(B) = AAACBB\),而\(2(3(A)C)2(B) = AAACAAACBB\)
给一个字符串,求它的最短折叠。例如\(AAAAAAAAAABABABCCD\)的最短折叠为:\(9(A)3(AB)CCD\)。
日常懵逼,不会
区间DP就这么结束了
接下来是背包和数位DP
背包
下午
因篇幅原因\(+\)过于基础,在这里我们跳过所有背包模板
例1
有\(n\)个人参加拔河比赛,要把他们分为两组,每人的实力为\(a_i\),一组的实力为这组人的实力之和。求两队实力差的最小值。(两队的人数没有限制)
题解:
以\(\frac{\sum_{i=1}^{n}a[i]}{2}\)为背包容量,跑一遍阉割版的01背包即可
例2
你有一个背包容积为\(V\),有\(n\)种不可分割的物品(每种\(p_i\)个),每件的体积为\(v_i\)和价值\(c_i\),你还有\(m\)种神奇的物品,它们的价值\(c_i=a*v^2+b*v+c\),\(v\)是你决定的这件物体体积(大于等于0)。求最优价值。
\(V<=1000,n<=1000,p_i<=1000,m<=5\)
题解:
两种背包分开跑,先跑神奇的物品,再用跑完的dp数组去跑多重背包
例3
有一家旅馆,有\(n\)间房间,每间可以住\(a_i\)人,需要\(b_i\)元。
有\(i\)个男人,\(j\)个女人来住宿,其中有\(k\)对夫妻,要求每间房间住的全是同性或者是一对夫妻(单人间无法住夫妻)。
问最少的总租金。
\(n,i,j<=300\)
题解:
一看到这道题,机房里就充满了快♂活的气息
首先,你需要想到:存在一种最优方案使得之多有一对夫妻在一件房间内。因为如果有两对,使两个男性住一间,两个女性住一间。
所以这道题里,我们只要考虑有一对夫妻就可以了
- 定义状态
\(dp[i][j][k][0/1]\)表示前\(i\)个房间里住了\(j\)个男性、\(k\)个女性、有没有夫妻 - 状态转移
\(dp[i][j][k][0]=min(dp[i-1][j][k][0],dp[i-1][j-a[i]][k][0]+b[i],dp[i-1][j][k-a[i]][0]+b[i])\)
\(dp[i][j][k][1]=min(dp[i-1][j][k][1],dp[i-1][j-1][k-1][0]+b[i],\) \(dp[i-1][j-a[i]][k][1]+b[i],dp[i-1][j][k-a[i]][1]+b[i])\)
例4
开始有一个数\(begin\),给一个长为\(n\)的序列\(c_i\),每次操作可以选择把开始的数加或减\(c_i\),变为新的数,之后在上一次的数的基础上加或减。要求每次操作之后的数要大于等于0,小于等于\(max\),求最后一次操作之后这个数的最大值。如果没有满足要求的解输出-1.
\(0 {<=} begin {<=} max {<=} 1000,1{<=}n{<=}50\)
题解:
- 定义状态
\(dp[i][j]\)表示\(i\)次操作后,数为\(j\)是否可行 - 状态转移
\(if\;\;dp[i][j]{==}1\)
\(dp[i+1][j+c[i]]=1(j+c[i]<=max)\)
\(dp[i+1][j-c[i]]=1(j-c[i]>=0)\)
例5
有\(n\)件物品,每件物品有体积\(v_i\),问装满体积\(V\)的方案数。答案对10取模。
但是你要输出:如果第i件物品消失了,装了体积为j的方案数。\(i=1...n,j=1...V\)
\(n,V<=1000\)
嗯,不会
数位
感觉数位DP有些迷,真心没怎么听懂
例1
给定一个十进制正整数N,写下从1开始,到N的所有正数,计算出其中出现所有1的个数。
例如:n = 12,包含了5个1。1,10,12共包含3个1,11包含2个1,总共5个1。
题解:
- 首先可以预处理出来如果后\(i\)位数字随便选,那么一共有多少个\(1\),记为\(f[i]\)。\(f[i]=i\times 10^{i-1}\)
分别计算如果前\(i\)位和\(n\)的前\(i\)位恰好相同,那么有多少个\(1\). - 如:
\(n=124056\) 第一位为0~1
考虑第一位为0时,那么之后的位可以随便选,对答案贡献\(f[5]\),而这一位的0不贡献答案
第一位为1时,那么之后的为不能随便选,只有\(24057\)种选法。这一位对答案的贡献是\(24057\),之后继续计算后五位对答案的贡献
此时第一位固定为\(1\)。第二位可以是\(0/1/2\)。
是0时,第2位不贡献答案,但是后面4位随便选。贡献\(f[4]\)
是1时,第2位贡献答案为\(10^4\)。后面4位随便选,贡献\(f[4]\)
是2时,第2位不贡献答案,后面4位不能随便选,答案不能直接计算,继续固定第二位,看第3位的数值
......
直到最后一位。
例2
找出1~n范围内含有13并且能被13整除的数字的个数
\(n<=10^{17}\)
我太菜了,van♂全不会
背包和数位就到此结束了
Day 3
接下来让我们进入状压DP
上午
例1
现有\(n\)盏灯,以及\(m\)个按钮。每个按钮可以同时控制这\(n\)盏灯——按下了第\(i\)个按钮,对于所有的灯都有一个效果。按下\(i\)按钮对于第\(j\)盏灯,是下面3中效果之一:如果\(a[i][j]\)为\(1\),那么当这盏灯开了的时候,把它关上;如果为\(-1\)的话,如果这盏灯是关的,那么把它打开;如果是\(0\),则无效果。
现在这些灯都是开的,给出所有开关对所有灯的控制效果,求问最少要按几下按钮才能全部关掉。
题解:
- emmmmm,这题只状压,不DP
- 存状态的时候状压
- 然后将能互相到达的状态之间连边,然后广搜最短路就好了
例2
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上、下、左、右,以及左上、左下、右上、右下八个方向上附近的各一个格子,共8个格子。
\(n<=9,k<=n^2\)
题解:
- 定义状态
肯定要状压
\(dp[i][k][j]\)表示前\(i\)行放了\(k\)个国王,j表示第\(i\)行的摆放方式 - 状态转移
我们要快速的判断摆放方式是否合法
用\(i\)表示某一行的状态 - 同一行
\(i\And (i { > > }1)\) - 相邻行的状态\(k\)
\(i\And k\)
\(i\And (k{ > > }1)\)
\(i\And (k{ < < }1)\)
以上的式子中如果有一个结果为1,说明无法转移
\(get[i]\)表示数字\(i\)的二进制位中\(1\)的数量,也就是第\(i\)行的国王数量,\(l\)表示\(i-1\)行国王的状态
\(dp[i][k][j]+=dp[i-1][k-get[i]][l]\)
状压完结散花
欢迎来到DP优化!
下午
除了例1,别的啥都不会,只会暴力DP……什么矩阵加速、数据结构维护,不存在的。
例1
斐波那契数列
\(f[1]=f[2]=1\)
\(f[n]=f[n-1]+f[n-2] (n {>=} 3)\)
求\(f[n]\mod(10^9+7)\)
\(n<=10^{18}\)
题解:
矩阵快速幂加速,不解释
例2
Codeforces 821E Okabe and El Psy Kongroo
你在\((0,0)\)。在\((x,y)\)时,每次移动可以到达\((x+1,y+1),(x+1,y),(x+1,y-1)\),平面上有\(n\)条线段,平行于\(x\)轴,参数为\(a_i,b_i,c_i\),表示在\((a_i,c_i)\)到\((b_i,c_i)\)的一条线段,保证\(b[i]=a[i+1]\),要求你一直在线段的下方且在\(x\)轴上方,即\(a_i {<= } x { <=} b_i\)时,\(0 { <= }y{ <= }c_i\)。问到达\((k,0)\)的方案数,对\(10^9+7\)取模。
\(n<=100,k<=10^{18},ci<=15\)
题解:
实现不是很会,但明白了做法,思路确实很神奇
直接DP就是:\(dp[i][j]=dp[i-1][j]+dp[i-1][j-1]+dp[i+1][j-1]\)
但是肯定会T,所以我们可以用矩阵来加速
\(\begin{bmatrix} 1 & 0 & 0 & 0\end{bmatrix}\) \(\begin{bmatrix} 1 & 1 & 0 & 0 \\ 1 & 1 & 1 & 0 \\ 0 & 1 & 1 & 1 \\ 0 & 0 & 1 & 1\end{bmatrix}\)
这样应该就可以了吧?(超级不自信)
例3
Openjudge Noi 9277 Logs Stacking
给出在最底层的木头的个数,问有多少种堆放木头的方式,当然你的堆放方式不能让木头掉下来.
在堆放的时候木头必须互相挨着在一起.
\(n <= 200000\)
题解:
- 正常解法
\(dp[i]=dp[i]+s[i]\)
其中s是dp的前缀和。 - 非正常解法
找规律 2333
例4
在连续的\(n\)秒中,在\(x\)轴上每秒会出现\(m\)个龙珠,出现之后会立即消失,知道了第一秒所在的位置,每从一个位置移动到另一个位置的时候,消耗的价值为\(abs(i-j)\), 拿到龙珠也要消耗一个价值(不同龙珠的价值不同),问\(n\)秒之后最少消耗多少价值。
\(m <= 500,n<=1000\)
题解:
虽然老师在上面讲+\(\mathfrak{Lancelot}\) dalao给我私下讲,但我还是有些迷,我太蒟了怎么破QAQ
- 暴力DP
\(dp[i][j]\)表示在\(i\)秒后,在第\(j\)个龙珠的位置上的最小代价。
\(dp[i][j]=min(dp[i-1][k]+abs(pos[i-1][k]-pos[i][j]))(k=1..m)+cost[i][j]\)
时间复杂度\(O(m^2n)\) - 优化DP
把绝对值拆开,变成向左走和向右走两种。
把当前时间的龙珠按位置排序,从左到右扫描,维护一个最小的\(s=dp[i-1][k]-pos[i-1][k]\),这样\(dp[i][j]=s+pos[i][j]+cost[i][j]\)
从右到左类似。
\(O(mnlogm)\)
例5
给你一个数组,每个值代表一种颜色,每次选一个区间删去,代价是区间内颜色种类数的平方,删完所有数组,问你最小代价是多少。
\(n<=50000\)
题解:
没怎么听因为听了也听不懂
但是应该是双向链表+DP。DP转移时直接从上一个最后一次出现的颜色那转移过来就可以。另外,区间的长度最多为\(\sqrt n\)个,也可以剪枝
时间复杂度\(O(n\sqrt{n})\)
DP优化也就这样了
嗯,树上DP了解一下
Day 4
上午
例1
某大学有\(N\)个职员,编号为1~N。他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数\(R_i\),但是呢,如果某个职员的上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。
\(n<=6000\)
题解:
树上DP经典题目
- 定义状态
\(dp[i][0/1]\)表示节点i不被选/被选所获得的最大快乐指数 - 边界条件
\(dp[leaf][1]=a[i]\)
\(leaf\)表示叶子结点,\(a[i]\)是原序列 - 状态转移
\(dp[from][0]+=max(dp[to][1],dp[to][0])\)
\(dp[from][1]+=dp[to][0]+a[from]\)
例2
树的直径
给一棵树,求树上最长路径的长度。
\(n<=500000\)
题解:
- 解法一:
- DP
考虑树上dp,确定一个根,一条路径一定存在一个深度最浅的节点。枚举每个点成为lca,看向子树伸出去的两条路径的长度和最长是多少。
\(dp[i][0/1]\)表示从i号点向下的最长/次长路径长度。
\(dp[i][0]=max(dp[son][0])+1\)注意只能用每个子树的最长路径更新i的最长和次长路径。即使一个子树次长路径很大,也不能更新,否则lca不是i。
\(O(n)\) - 解法二:
- 随便找一个点,用\(\mathcal{dfs}\)找到离这个点最远的点\(i\),再用\(\mathcal{dfs}\)找离\(i\)最远的点\(j\)。\(i\)到\(j\)的路径是一条直径。
证明正确性吗?不存在的,我懒得打了(逃。
P.S. 法二不能用于负边权
例3
有\(n\)节课可以选,每节课有至多一个前置课程,和这节课的学分,问如果只能选\(m\)节课,最多有多少学分。
\(n<=300\)
题解:
- 我说这题是树上跑背包你信么?
- 算法主体
int dp[310][310],a[310],n,m; //dp[i][j]表示a[ ]
void dfs(int f){
dp[f][1]=a[f];
for(int i=head[f];i!=-1;i=tree[i].nex){
int to=tree[i].t; dfs(to);
for(int j=m;j>=1;j--){
for(int o=j-1;o>=1;o--)
dp[f][j]=max(dp[f][j],dp[f][j-o]+dp[to][o]);
}
}
}
例4
给定一颗树,每条边有一个权值w,问切掉哪条边之后,分成的两颗树的较大的直径*切掉边的权值最小?如果存在多条边使得结果相同,输出边id最小的。
\(n<=100000\)
题解:
我们要分类讨论一下:
- 要切的边在不在直径上
我们直接用那条边的权值去乘直径就可以了 - 要切的边在直径上
这时就需要我们能够快速的算出切开后两部分的的直径
要解决这个问题,我们要先算出图的一条直径,记下起点\(s\)和终点\(t\),然后分别以\(s\)和\(t\)为根,去找直径,处理出来的\(dp[u]\)就是\(fa[u]->u\)被切开后的直径
例4
Codeforces 219D Choosing Capital for Treeland
给一棵树,每条边有方向,改变一条边方向的代价是1.
对于一个点,如果选它为根,那么需要把方向不对的边改变方向(都变成深度小的点指向深度大的点)。
问选一个点为根的最小代价。和选哪些点的代价是这个数字。
\(n<=200000\)
题解:
\(dp[u]\)表示以\(u\)为根要调整的次数,再记录下方向
一遍DFS,同向加,反向减
例5
给一个环套树森林,求最大权独立集。(就是相邻的点不能同时选)
\(n<=1000000\)
题解:
先找到那个环。随便找两个点,暴力枚举这两个点选不选。之后变成森林。
这是老师PPT上的题解,就一句话,十分简练。
嗯,然后我就懵逼了……
树型DP就这么在懵逼中结束了,接下来有更懵逼的……
期望DP是什么?能吃吗?斜率优化又是什么?
讲真,下午彻底懵逼
下午
例1
开始有一个吸血鬼,n-1个平民百姓。每天等概率选出两个人,如果是一个吸血鬼一个平民,平民以p的概率被转化为吸血鬼,否则什么也不发生。问每个人都变成吸血鬼的天数期望。
\(n<=100000\)
题解:
设\(dp[i]\)为有\(i\)个吸血鬼的话还期望需要多少天完成。
设\(p[i]\)为此时转换了一个平民的概率。
\(p[i]=\frac{2\times (n-i)\times i}{n\times(n-1)}\times p\)
\(dp[i]=p[i]\times dp[i+1]+(1-p[i])\times(dp[i])+1\)
例2
在\(x\)轴上,你现在的起点在\(1\)处。在\(N\)个点处布有地雷,\(1<=N<=10\)。地雷点的坐标范围:\([1,100000000]\).
每次前进\(p\)的概率前进\(1\)步,\(1-p\)的概率前进\(2\)步。问顺利通过这条路的概率。就是不要走到有地雷的地方。
题解:
- 设\(dp[i]\)表示一条路径经过i的概率。
\(dp[1]=1,dp[i]=p\times dp[i-1]+(1-p)\times dp[i-2]\)
快速幂算出\(1-dp[x_1]\)就是安全通过第一个地雷的概率,此时在\(x_1+1\),你又要通过\(x_2,...x_n\),就是把\((x_1+1)\text{~}x_2\)再做一次,全都安全通过的概率乘起来。 - 嗯,以上全都是抄的老师的PPT
例3
现在在一个\(n\times m\)规模的区域上从\('@'\)处出发,每次都随机向前后左右四个方向中选择可以走的方向进入('#'不可走, 不能越过边界)。现在问到达终点$的期望步数, 终点可能有多个, 输入保证一定有起点\(n,m<=15\), 如果无法到达任何一个终点输出\(-1\)
题解:
高斯消元,爱信不信。
例4
给一个数列ai,要求划分成若干段,一段的代价是\(\sum a_i^2+M\),总的代价是每段代价和。
求最小总代价。
\(n<=500000\)
题解:
- 嗯,真·斜率优化
- 嗯,真·不会
- 嗯,还是把老师的代码放上来吧
膜拜一下老师 orz
#include <iostream>
#include <stdio.h>
using namespace std;
int n , m , sum[500010] , q[500010] , dp[500010] , head , tail;
int getup ( int x ) {
return dp[x] + sum[x]*sum[x];
}
int getdown ( int x ) {
return 2*sum[x];
}
void work () {
int i;
for ( i = 1 ; i <= n ; i++ ) scanf ( "%d" , &sum[i] );
sum[0] = dp[0] = 0;
for ( i = 1 ; i <= n ; i++ ) sum[i] += sum[i-1];
q[1] = 0; head = tail = 1;
for ( i = 1 ; i <= n ; i++ ) {
//斜率优化
while ( head < tail && getup(q[head+1]) - getup(q[head]) <= sum[i]*(getdown(q[head+1])-getdown(q[head])) ) head++;
dp[i] = dp[q[head]] + ( sum[i] - sum[q[head]] ) * ( sum[i] - sum[q[head]] ) + m;
while ( head < tail && ( getup(q[tail]) - getup(q[tail-1])) * ( getdown(i) - getdown(q[tail]) ) >=
( getup(i) - getup(q[tail]) ) * ( getdown(q[tail]) - getdown(q[tail-1]) ) ) tail--;
q[++tail] = i;
}
printf ( "%d\n" , dp[n] );
}
int main () {
while ( scanf ( "%d%d" , &n , &m ) != EOF ) work ();
return 0;
}
至此,DP应该就告一段落了。
我DP掌握的还是不够好,以后也要多加锻炼。