CF 659 D - Sum of Paths dp E Tree树上差分

D - Sum of Paths

题目链接
D题题意是给一个数组,刚开始随便在一个点,之后随机向左向右走。只能在 1 − n 1 - n 1n中走(不能越界),每走到一个点就加上这个位置的值,问走的所有路径的权值总和是多少。有m次查询,每次修改一个位置上的权值。
在这里插入图片描述
在这里插入图片描述

这题很显眼,就是统计每个点总过走多少次。(在所有的路径中出现的次数)于是可以想到 d p [ i ] [ j ] dp[i][j] dp[i][j]表示走 j j j步后到 i i i点的种类数,转移方程: d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + d p [ i + 1 ] [ j − 1 ] dp[i][j] = dp[i - 1][j - 1] + dp[i + 1][j - 1] dp[i][j]=dp[i1][j1]+dp[i+1][j1].刚开始想的把 d p [ i ] [ 1 − k ] dp[i][1 - k] dp[i][1k]全加起来就是 i i i出现的个数,但是这不对!!!!!,漏了,漏了什么?例如刚开始的时候, d p [ i ] [ 0 ] dp[i][0] dp[i][0]初始化为1,但是并不是1,因为之后的路径不一样。刚开始的得算好多次。。
于是怎么办? 看了题解。。。 发现 d p dp dp数组也可以是还差 j j j步的时候在 i i i点。。。所以最后出现的个数是: d p [ i ] [ j ] ∗ d p [ i ] [ k − j ] , j ∈ [ 0 , k ] dp[i][j] * dp[i][k - j],j∈[0,k] dp[i][j]dp[i][kj],j[0,k].
。。。

#include<stdio.h>
#include<set>
#include<algorithm>
#include<vector>
#include <queue>
using namespace std;
#define mkp make_pair
#define st first
#define sd second
#define pb push_back
typedef long long ll;
typedef pair<int,int> pii;

const int inf = 0x3f3f3f3f;
const int maxn = 5000+105;
const ll mod = 1e9+7;
ll num[maxn];
ll dp[maxn][maxn];
ll a[maxn];
int main()
{
	int n, k, m;
	scanf("%d%d%d",&n,&k,&m);
	for (int i = 1; i <= n; i ++ )
	{
		scanf("%lld",&a[i]);
	}
	for (int i = 1; i<= n; i ++ )
		dp[i][0] = 1;
	for (int i = 1; i <= k; i ++ )
	{
		for (int j = 1; j<= n; j ++ )
		{
			dp[j][i] = dp[j + 1][i - 1] + dp[j - 1][i - 1];
			dp[j][i] %= mod;
		}
	}
	for (int  i= 1;i <= n; i ++ )
	{
		for (int j = 0; j <= k; j ++ )
		{
			num[i] += dp[i][j] * dp[i][k - j] % mod;
			num[i] %= mod;
		}
		// printf("%d ",num[i]);
	}
	// printf("\n");
	ll ans =0 ;
	for (int i = 1; i <= n; i ++ )
	{
		ans = (ans + num[i] * a[i] % mod) % mod;
	}
	while(m -- )
	{
		int p;
		ll x;
		scanf("%d%lld",&p,&x);
		ans += (x - a[p]) * num[p] % mod;
		ans %= mod;
		ans += mod;
		ans %= mod;
		a[p] = x;
		printf("%lld\n", ans);

	}
}

E. Distinctive Roots in a Tree

题目链接
题意:
给一个树,有点权。如果 从这个点到其他所有点的路径中没有重复的点权那么这个点就是好的,问有多少个点是好的。
刚开始想的:
如果两个点点权一样,那么这两个点的子树都不能是好的。但是没法实现。并且注意到:如果有3个点一样并且在一条路径上,那么所有点都不是好的。
但是如果换根dp,不知道dp要维护一些什么东西。。。
题解:
差分,把不好的点分为两类:

  1. 如果这个点的颜色在这个子树中出现了,那么除了这个子树,别的点都不是好的。
  2. 如果这个点的颜色没有全部包含在这个子树中,那这个子树都不是好的。
  3. 这两种情况可以覆盖所有不好的情况。
    于是就简单了。。
    因为都是子树的问题,所以直接在dfs序上差分就好。
#include<stdio.h>
#include<set>
#include<algorithm>
#include<vector>
#include <queue>
using namespace std;
#define mkp make_pair
#define st first
#define sd second
#define pb push_back
typedef long long ll;
typedef pair<int,int> pii;

const int inf = 0x3f3f3f3f;
const int maxn = 2e5+105;
const ll mod = 1e9+7;
int l[maxn];
int r[maxn];
int a[maxn];
std::vector<int> vv[maxn];
int pos = 0;
void dfs(int x,int fa)
{
	l[x] = ++pos;
	for(int i = 0; i < vv[x].size(); i ++ )
	{
		int v = vv[x][i];
		if(v == fa) continue;
		dfs(v,x);
	}
	r[x] = pos;
}
int num[maxn];
int s[maxn];
int cha[maxn];
void Dfs(int x,int fa)
{
	int per = num[a[x]];
	num[a[x]] ++ ;
	for (int i =0 ; i < vv[x].size(); i ++ )
	{
		int v = vv[x][i];
		if(v == fa)
			continue;
		int p = num[a[x]];
		Dfs(v,x);
		int q = num[a[x]];
		if(q > p)
		{
			// 如果子树中有这个颜色  那么除了这个子树 别的都不能用
			int lp = l[v];
			int rp = r[v];
			cha[1] ++ ;
			cha[lp] -- ;
			cha[rp + 1] ++ ;
		}
	}
	if(num[a[x]] - per != s[a[x]])
	{
		// 除了这个子树 ,别的地方还有这个颜色,那这个子树不可用
		cha[l[x]] ++ ;
		cha[r[x] + 1] -- ;
	}
}
std::vector<int> vp;

int main()
{
	int n;
	scanf("%d",&n);
	for (int i= 1; i <= n; i ++ )
	{
		scanf("%d",&a[i]);
		vp.pb(a[i]);
	}
	sort(vp.begin(),vp.end());
	vp.erase(unique(vp.begin(),vp.end()),vp.end());
	for (int i = 1; i <= n; i ++ )
	{
		a[i] = lower_bound(vp.begin(),vp.end(),a[i]) - vp.begin() + 1;
		s[a[i]] ++ ;
	}
	for(int i = 1; i < n; i ++ )
	{
		int x,y;
		scanf("%d%d",&x,&y);
		vv[x].pb(y);
		vv[y].pb(x);
	}
	dfs(1,0);
	Dfs(1,0);
	int ans =0 ;
	for (int i = 1; i <= n; i ++ )
	{
		cha[i] += cha[i - 1];
		if(cha[i] == 0)
			ans ++ ;
	}
	printf("%d\n", ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值