zoukankan      html  css  js  c++  java
  • 清北学堂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 eq k))

    例5

    Codeforces 467C George and Job

    给定一个长度为n的序列,从序列中选出k个不重叠且连续的m个数,要求和最大。
    (1<=m imes 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 (找不到这道题 ( m{Orz}))

    有一块矩形大蛋糕,长和宽分别是整数(w ,h)。现要将其切成(m)块小蛋糕,每个小蛋糕都必须是矩形、且长和宽均为整数。切蛋糕时,每次切一块蛋糕,将其分成两个矩形蛋糕。请计算:最后得到的(m)块小蛋糕中,最大的那块蛋糕的面积下限。
    (w,h,m <= 20)

    题解:

    • 定义状态
      (dp[i][j][k])表示长宽为(i,j)的蛋糕切(k)刀的答案
    • 边界条件
      (dp[i][j][0]=i imes 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 imes b_2+3 imes b_3+4 imes 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)的左子树的加分( imes 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 imes 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)表示某一行的状态
    • 同一行
      (iAnd (i { > > }1))
    • 相邻行的状态(k)
      (iAnd k)
      (iAnd (k{ > > }1))
      (iAnd (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,所以我们可以用矩阵来加速

    (egin{bmatrix} 1 & 0 & 0 & 0end{bmatrix}) (egin{bmatrix} 1 & 1 & 0 & 0 \ 1 & 1 & 1 & 0 \ 0 & 1 & 1 & 1 \ 0 & 0 & 1 & 1end{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(nsqrt{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 imes (n-i) imes i}{n imes(n-1)} imes p)
    (dp[i]=p[i] imes dp[i+1]+(1-p[i]) imes(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 imes dp[i-1]+(1-p) imes dp[i-2])
      快速幂算出(1-dp[x_1])就是安全通过第一个地雷的概率,此时在(x_1+1),你又要通过(x_2,...x_n),就是把((x_1+1) ext{~}x_2)再做一次,全都安全通过的概率乘起来。
    • 嗯,以上全都是抄的老师的PPT

    例3

    hdu 2262 Where is the canteen

    现在在一个(n imes 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
    " , dp[n] );
    }
    int main () {
        while ( scanf ( "%d%d" , &n , &m ) != EOF ) work ();
        return 0;
    }
    
    

    至此,DP应该就告一段落了。

    我DP掌握的还是不够好,以后也要多加锻炼。

  • 相关阅读:
    c# winForm 圆角Panel
    C# WinForm开发系列 ListBox/ListView/Panel
    WCF RIA Service实体类的自定义复杂类型属性在客户端不可见
    与用户深入交互 不做机器人般的设计师
    The Big List of What’s New or Improved in Silverlight 5
    Instantiating a Silverlight Plugin (Using CreateSilverlight.js and Silverlight.js)
    不用js也能创建silverlight
    WCF中使用HttpContext.Current的办法
    去除字符串空格,各类型验证,获取url参数等
    Understanding WCF Services in Silverlight 2
  • 原文地址:https://www.cnblogs.com/morslin/p/11853367.html
Copyright © 2011-2022 走看看