清北学堂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

51nod 1354 选数字

当给定一个序列\(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

51nod 1294 修改数组

给出一个整数数组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

Codeforces 407B Long Path

\(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

NOIp2010 乌龟棋

在一行\(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

NOIp2015 子串

有两个仅包含小写英文字母的字符串\(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

NOI1995 石子合并

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

NOIp2003 加分二叉树

设一个\(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

SCOI2003 字符串折叠

折叠的定义如下:

一个字符串可以看成它自身的折叠。记作\(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

Luogu P1782 旅行商的背包

你有一个背包容积为\(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

Vijos P1240 朴素的网络游戏

有一家旅馆,有\(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

HAOI2012 音量调节

开始有一个数\(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

Bzoj 2287 消失之物

\(n\)件物品,每件物品有体积\(v_i\),问装满体积\(V\)的方案数。答案对10取模。
但是你要输出:如果第i件物品消失了,装了体积为j的方案数。\(i=1...n,j=1...V\)
\(n,V<=1000\)

嗯,不会


数位

感觉数位DP有些迷,真心没怎么听懂

例1

51nod 1009 数字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

Hdu 3652 B-number

找出1~n范围内含有13并且能被13整除的数字的个数
\(n<=10^{17}\)

我太菜了,van♂全不会

背包和数位就到此结束了


Day 3

接下来让我们进入状压DP

上午

例1

Luogu P2622 关灯问题II

现有\(n\)盏灯,以及\(m\)个按钮。每个按钮可以同时控制这\(n\)盏灯——按下了第\(i\)个按钮,对于所有的灯都有一个效果。按下\(i\)按钮对于第\(j\)盏灯,是下面3中效果之一:如果\(a[i][j]\)\(1\),那么当这盏灯开了的时候,把它关上;如果为\(-1\)的话,如果这盏灯是关的,那么把它打开;如果是\(0\),则无效果。
现在这些灯都是开的,给出所有开关对所有灯的控制效果,求问最少要按几下按钮才能全部关掉。

题解:

  • emmmmm,这题只状压,不DP
  • 存状态的时候状压
  • 然后将能互相到达的状态之间连边,然后广搜最短路就好了

例2

SCOI2005 互不侵犯

在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

Hdu 4362 Dragon Ball

在连续的\(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

Hdu 5009 Paint Pearls

给你一个数组,每个值代表一种颜色,每次选一个区间删去,代价是区间内颜色种类数的平方,删完所有数组,问你最小代价是多少。
\(n<=50000\)

题解:

没怎么听因为听了也听不懂

但是应该是双向链表+DP。DP转移时直接从上一个最后一次出现的颜色那转移过来就可以。另外,区间的长度最多为\(\sqrt n\)个,也可以剪枝
时间复杂度\(O(n\sqrt{n})\)

DP优化也就这样了


嗯,树上DP了解一下

Day 4

上午

例1

Luogu P1352 没有上司的舞会

某大学有\(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

Luogu P2014 选课

\(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

Hdu 4079 Terrorist’s destroy

给定一颗树,每条边有一个权值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

ZJOI2008 骑士

给一个环套树森林,求最大权独立集。(就是相邻的点不能同时选)
\(n<=1000000\)

题解:

先找到那个环。随便找两个点,暴力枚举这两个点选不选。之后变成森林。

这是老师PPT上的题解,就一句话,十分简练。

嗯,然后我就懵逼了……

树型DP就这么在懵逼中结束了,接下来有更懵逼的……


期望DP是什么?能吃吗?斜率优化又是什么?

讲真,下午彻底懵逼

下午

例1

Zoj 3551 Bloodsucker

开始有一个吸血鬼,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

Poj 3744 Scout YYF I

\(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

hdu 2262 Where is the canteen

现在在一个\(n\times m\)规模的区域上从\('@'\)处出发,每次都随机向前后左右四个方向中选择可以走的方向进入('#'不可走, 不能越过边界)。现在问到达终点$的期望步数, 终点可能有多个, 输入保证一定有起点\(n,m<=15\), 如果无法到达任何一个终点输出\(-1\)

题解:

高斯消元,爱信不信。

例4

hdu 3507 Print Article

给一个数列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掌握的还是不够好,以后也要多加锻炼。

posted @ 2019-11-13 21:33  MorsLin  阅读(343)  评论(0编辑  收藏  举报