树上Dp

河流(树上Dp)

题意

给你n+1个点,n条边,保证构成一棵树,然后再这n个上建造k个中转站,(0默认有,所以是n 个点)。每个点有w\([i]\)个需要中转的物品,求最后全部中转完的最短距离。

难点

如果我枚举当前建造了多少个中转站,但是我们不知道当前这个点有没有中转站,所以我们需要开两个dp,把两种情况分开讨论,\(d[i][j][k]\)表示以 i 为根结点 已经建造了 j 个中转站 同时以 k 为中转结点的最小代价,所以我们根据出栈入栈的顺序,枚举当前的根节点。

转移

\(d[i][v1][k]\)=\(min(d[i][v1-v2][k]+d[j][v2][k],O.o)\)
\(d2[i][v1][fa]\)=\(min(d2[i][v1-v2][fa]+d[j][v2][u],O.o)\)
\(d[i][j][fa]\)=\(min(d2[i][j-1][fa],O.o)\)

Code

const int N=120,mod=1e9+7;
int lowbit(int x){return x&-x;}
int gcd(int a,int b){return a%b==0?b:gcd(b,a%b);}

int d[N][N][N];// 当前第i个 建了j个 同时以 k为伐木场(k!=i
int d2[N][N][N];	//当前第i个 建了j个 同时以 k为伐木场(k==i
int w[N];  
vector<pi>v[N];// 存边
int s[N],tt;//用栈来找出 可能是 中转站的点。
int n,m,de[N];// de 是用来找出距离的

void dfs(int u,int pr)
{
	s[++tt]=u;// 入栈
	for(pi t:v[u])
	{
		int j=t.x,y=t.y;
		if(j==pr)continue;
		de[j]=de[u]+y;
		dfs(j,u);
		
		for(int now=1;now<=tt;now++)//
		{
			int fa=s[now];// 枚举 可能的中转站
			for(int j2=m;j2>=0;j2--)// 从大到小,经典背包O.o
			{
                // 这一步就相当于预处理了。
                
				d[u][j2][fa]+=d[j][0][fa];//先找出最差的情况。j中一个中转站都没有
				d2[u][j2][fa]+=d[j][0][u];//同上
				
				for(int j3=1;j3<=j2;j3++)
				{
             // 枚举 儿子中有多少个中转站,如果u是中转站,那么他的儿子肯定都去u,否则会去fa
                  d[u][j2][fa]=min(d[u][j2][fa],d[u][j2-j3][fa]+d[j][j3][fa]);
                  d2[u][j2][fa]=min(d2[u][j2][fa],d2[u][j2-j3][fa]+d[j][j3][u]);
				}
			}	
			
		}	
	}

    // 然后处理一下 当前点到 可能是中转站的 距离。
	for(int i=1;i<=tt;i++)
	{
		int fa=s[i];
		
		for(int j=0;j<=m;j++)
		{
			d[u][j][fa]+=(de[u]-de[fa])*w[u];
			if(j>=1)d[u][j][fa]=min(d[u][j][fa],d2[u][j-1][fa]);
            // d2 为啥要减1? 因为d 保证u 能是根节点,然后他的子树中就只有j-1个了
		}
	}
	tt--;//出栈
}



void solve()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		w[i]=a;
		v[b].ps({i,c});
	}
	dfs(0,-1);
	cout<<d[0][m][0]<<endl;
}

signed main()
{
	kd;
	int _;_=1;
	//cin>>_;
	while(_--)solve();	
	return 0;
}

括号树

题意

树上每个点上有一个括号,让你算出改点到根节点的合法的子序列个数,最后亦或起来。

经典小芝士

当我们记录了,一个点的最近的匹配的 ( 标为s[x] ,那么 这段区间的 代价就是 \(w[fa[s[x]]]\)+1,然后捏 如果我们按照dfs 序进行 出栈入栈的化这个括号序列也就对应了,这个简单路径上的括号序列。

思路

对于每个点,记录一下他的结果,和他的父节点和他的能匹配的最近的 ( 。然后递归处理,如果一个点 是( 那么它的 匹配的情况就 改成自己, 如果不是 ( 同时有 能匹配的 ) 就加上这一段的 值。最后算一个前缀和,因为他求的是子串,如果 我爹有,那我也有。O.o

void dfs(int u,int pr)
{
	s[u]=s[fa[u]];// 把他的最近左括号标成它父亲的。
	if(w[u]==1)s[u]=u;// 如果我自己是左括号。就记录一下我能匹配的最近的左括号是自己
	else if(s[u])ans[u]=ans[fa[s[u]]]+1,s[u]=s[fa[s[u]]];
	// 如果是 ) 同时有可以匹配的情况,那么他的情况就是 匹配的括号的上一个位置的 结果 +1.
	// 加上上一步的情况 同时加上当前匹配成功的一种情况。
	for(int j:v[u])
	{
		dfs(j,u);
	}
}

Code

const int N=2e6+100,mod=1e9+7;
int lowbit(int x){return x&-x;}
int gcd(int a,int b){return a%b==0?b:gcd(b,a%b);}

int w[N];
int s[N];
int d[N],fa[N];
int ans[N];
vector<int>v[N];

void dfs(int u,int pr)
{
	s[u]=s[fa[u]];
	if(w[u]==1)s[u]=u;
	else if(s[u])ans[u]=ans[fa[s[u]]]+1,s[u]=s[fa[s[u]]];
	
	for(int j:v[u])
	{
		dfs(j,u);
	}
}


void solve()
{
	int n;cin>>n;
	for(int i=1;i<=n;i++)
	{
		char x;cin>>x;
		if(x=='(')w[i]=1;
		else w[i]=0;
		
	}
	for(int i=2;i<=n;i++)
	{
		int x;cin>>x;
		fa[i]=x;
		v[x].ps(i);
	}
	
	dfs(1,0);
	int res=ans[1];
	for(int i=2;i<=n;i++)
	{
		// cout<<i<<" "<<ans[i]<<endl;
		ans[i]+=ans[fa[i]];
		res^=(i*ans[i]);
	}
	cout<<res<<endl;
}

signed main()
{
	kd;
	int _;_=1;
	//cin>>_;
	while(_--)solve();	
	return 0;
}

C. Vertex Deletion

题意:

你可以删除一些点,但要满足删除之后其他的剩余的点都要有至少一个点和他直接相邻

思路:

O.o

一开始想少了。我以为只要考虑当前点要不要然后进去讨论,发现根本转移不过来,因为在转移 当前点要的情况,不知道子树的情况。然后看了题解发现需要记录一下要的情况,当前的子树要的要的情况。剩下的都在代码注释中详细解释一下

Code

const int N = 1e5 + 100, mod = 998244353, INF = 1e10;
int lowbit(int x) { return x & -x; }
int gcd(int a, int b) { return a % b == 0 ? b : gcd(b, a % b); }
vector<int> v[N];

// 要各个结点的情况
/*
不删和全删 +2
表示i 点删除了
1 表示 i点没删除 但是有子树
2 表示 i点没删除 同时没有子树
如果当前点删了 
他的儿子被删除了,要么就有其他的子树,不然不合法
d[u][0]=(d[j][0]*(d[j][1]));
如果他的没有被删除
d[u][2]好转移:d[u][2]=(d[u][2]*d[j][0]);因为没有子树 就要全部删除
d[u][1]直接算有点复杂,这个点没有被删除的全部情况就是 
d[u][1]*(d[j][0]+d[j][1]+d[j][2]),
减去 d[u][2]也就是所有的情况-该点没被删除但是没有子树的情况== 该点没被删除但是有子树的情况

*/
int d[N][3];

void dfs(int u, int pr) {
  d[u][0] = 1, d[u][1] = 1, d[u][2] = 1;
  for (int j : v[u]) {
    if (j == pr) continue;
    dfs(j, u);
    d[u][0] = d[u][0] * (d[j][1] + d[j][0]) % mod;
    d[u][2] = (d[u][2] * d[j][0]) % mod;
    d[u][1] = d[u][1] * (d[j][0] + d[j][1] + d[j][2]) % mod;
  }
  d[u][1] -= d[u][2];
  d[u][1] = (d[u][1] % mod + mod) % mod;
}
void solve() {
  int n;
  cin >> n;
  for (int i = 1; i <= n; i++) 
      v[i].clear(), d[i][0] = d[i][1] = d[i][2] = 0;
  for (int i = 1; i < n; i++) {
    int a, b;
    cin >> a >> b;
    v[a].ps(b);
    v[b].ps(a);
  }
  dfs(1, -1);
  cout << (d[1][0] + d[1][1]) % mod << endl;
}

signed main() {
  kd;
  int _;
  _ = 1;
  cin >> _;
  while (_--) solve();
  return 0;
}
posted @ 2022-08-11 21:44  黄小轩  阅读(36)  评论(0)    收藏  举报