20230703-动态规划DP 1

20230703

热身

题目

求长度为n的合法括号序列有多少个,对\(10^9+7\)取模。
\(n\)为偶数,\(n\le 10^6\)

Solution

可以维护一个栈
遇到一个左括号就加入栈
而遇到右括号时就取栈顶的左括号与它配对出栈
一个合法序列需要保证:

  1. 最后栈为空,即所有的左括号都和有括号配对了
  2. 中间不能出现遇到一个右括号时栈里没有左括号了

这样我们可以把栈里的括号数量转化到平面直角坐标系上面
这样可以得到一条折线
而这条折线就对应一个括号序列
它是合法的当且仅当:

  1. 没有一部分跑到了x轴下方
  2. 最后折线结束在(n,0)的位置

image
图中这个序列是不合法的,因为红色圈起来的地方在x轴下方了

我们考虑将这样的折线转化成横平竖直的变化
考虑再建立一个平面直角坐标系
把原来的也放进去
我们把x轴逆时针旋转45度
得到了一条直线\(l1:y=x\)
那么我们每一次的变化可以转化为从原点向前走和向上走
但是不能超过这条直线\(l1:y=x\)
最终走到\((n,n)\)
由于不超过很难去判断
我们可以在设一条直线\(l2:y=x+1\)
这样走的路径不碰到\(l2\)即可

而对于每一个碰到\(l2\)的折线
我们考虑把它沿着\(l2\)翻折
这样起点\((0,0)\)就变到了\((-1,1)\)
\((0,0) \to (n,n)\)的方案中
有部分合法有部分不合法
而那部分不合法的我们把它翻折过去
成了\((-1,1) \to (n,n)\)的方案
这样方案数就是
\((0,0) \to (n,n)的方案数 - (-1,1) \to (n,n)的方案数\)

再来考虑怎么计算
\((0,0) \to (n,n)\)我们一共会走\(2n\)
而每一步我们可以选择向上和向右之间的一种
而一共需要\(n\)步向上和\(n\)步向右
方案数相当于在\(2n\)个东西中选\(n\)个向上走的方案数
也就是\(C_{2n}^{n}\)

同理
\((-1,1) \to (n,n)\)的方案数就是
\(C_{2n}^{n-1}\)

这样答案就为\(C_{2n}^{n}-C_{2n}^{n-1}\)
这个数也就是卡特兰数

树形DP

CF581F Zublicanes and Mumocrates

题目大意

传送门
给出一棵n个点的树,保证它有偶数个叶子节点,请对该树的所有节点进行黑白染色,使得黑色与白色的叶子节点数相等,且直接连接两个异色点的边数最少。
\(n \le 5000\)

Solution

很明显的树形DP
考虑\(f[u][i][0/1]\)表示在节点\(u\)的子树中
叶子结点为黑色的有\(i\)个,当前节点颜色为黑色1白色0的最小边数
考虑依次枚举\(u\)的每一个儿子\(v\)
那么我们的\(f\)可以由上一个儿子更新的状态转移过来
那么
\(f[u][i+j][0]=f[u][i][0]+min(f[v][j][0],f[v][j][1]+1)\)
\(f[u][i+1][1]=f[u][i][1]+min(f[v][j][1],f[v][j][0]+1)\)

但是发现以前的\(f\)是很有可能被改变的
而组成\(k=i+j\)也有很多种情况
所以在每一次更新前
我们需要再用一个数组\(g[i][j]=f[u][i][j]\)来记录上一次的情况
同时将\(f[u][i][j]\)初始化为inf
在更新过程中每次取min即可

还要注意需要两次dfs
第一次来预处理,扫一遍数统计叶子结点的个数
而我们不能用叶子结点为根,所以根很有可能不是1
得知叶子结点个数就可以知道dp枚举的范围
第二次在进行数形DP即可

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e4+10;
int n,head[maxn],tot=0,f[5005][5005][2],in[maxn],cnt=0,rt,siz[maxn],g[5005][2];
struct edge{
  int v,nxt;
}e[maxn*2];

inline void add(int u,int v){
  e[++tot]=(edge){v,head[u]};
  head[u]=tot;
  e[++tot]=(edge){u,head[v]};
  head[v]=tot;
}

inline int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

inline void dfs1(int u,int fa){
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;in[u]++;
  	if(v==fa) continue;
  	dfs1(v,u);
  }
  cnt+=(in[u]==1);
}

inline void dfs2(int u,int fa){
  if(in[u]==1){
  	f[u][0][0]=f[u][1][1]=0;siz[u]=1;
  	return;
  }
  bool flag=false;siz[u]=1;
  for(int i=head[u];i;i=e[i].nxt){
  	int v=e[i].v;
  	if(v==fa) continue;
  	dfs2(v,u);
  	
  	if(!flag){
  	  for(int j=0;j<=siz[v];j++)
		 f[u][j][0]=min(f[v][j][0],f[v][j][1]+1),
		 f[u][j][1]=min(f[v][j][1],f[v][j][0]+1);	
	}
	else{
	  for(int j=0;j<=min(siz[u],cnt/2);j++)
	    for(int k=0;k<2;k++)
		  g[j][k]=f[u][j][k],f[u][j][k]=0x3f3f3f3f;//初始化,后面dp的时候f会改变 
	  for(int j=0;j<=min(siz[v],cnt/2);j++)
	    for(int k=0;k<=min(siz[u],cnt/2-j);k++)
	      f[u][j+k][0]=min(f[u][j+k][0],g[k][0]+min(f[v][j][0],f[v][j][1]+1)),
	      f[u][j+k][1]=min(f[u][j+k][1],g[k][1]+min(f[v][j][1],f[v][j][0]+1));
	}
	flag=true;siz[u]+=siz[v];
  }
}

int main(){
  /*2023.7.4 H_W_Y CF581F Zublicanes and Mumocrates 树形DP*/ 
  n=read();
  for(int i=1;i<n;i++){
  	int u=read(),v=read();
  	add(u,v);
  }
  dfs1(1,0);rt=1;
  while(in[rt]==1) rt++;
  memset(f,0x3f,sizeof(f));
  dfs2(rt,0);
  printf("%d\n",min(f[rt][cnt/2][0],f[rt][cnt/2][1]));
  return 0;
}

数位DP(Digit DP)

状压DP

posted @ 2023-07-19 22:43  H_W_Y  阅读(15)  评论(0编辑  收藏  举报