【题解】CF575B Bribes

由题可知这是一棵树,因此求每一条边经过的次数可以通过树上差分解决。而现在只有部分(有向)边需要花费,因此就需要找到一种能够记录单向花费的信息。考虑到一条边连接的两个点因在树上而深度不同,所以可以分为叶子指向父节点与父节点指向叶子两种边。形象化地,第一种可以称为上行边,第二种称为下行边。

首先通过树上差分,将信息分别储存在上行边的起点与下行边的中点。

for (int i = 1;i <= k;++i)
{
	int lca = getLCA (a[i],a[i - 1]);
	++up[a[i - 1]];++down[a[i]];//up 加的位置为起点,down 加的位置为终点
	--up[lca];--down[lca];
} 

然后通过一次 dfs 和差分数组还原每一条边经过的次数。最后统计答案,对于一条 v→uv \to uvu 需要花费的边,若是上行边,那么通过的次数记录在 vvv 上,否则是 uuu 上。设某条边经过了 xxx 次,则总花费为 1+2+⋯+2x−1=2x−11 + 2 + \cdots + 2^{x - 1} = 2^x - 11+2++2x1=2x1,所以直接预处理 222 的幂次即可。

总代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
#include <map>
#define init(x) memset (x,0,sizeof (x))
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
using namespace std;
const int MAX = 1e5 + 5;
const int MOD = 1e9 + 7;
inline int read ();
int n,k,a[MAX * 10],f[MAX][20],dep[MAX],up[MAX],down[MAX];
vector <int> ve[MAX];
map <pair <int,int>,int> co;
ll ans,pw[MAX * 10];
int getLCA (int x,int y);
void dfs (int u,int fa);
int main ()
{
	//freopen (".in","r",stdin);
	//freopen (".out","w",stdout);
	n = read ();
	for (int i = 1;i < n;++i)
	{
		int x = read (),y = read (),ty = read ();
		ve[x].push_back (y);co[{x,y}] = 0;
		ve[y].push_back (x);co[{y,x}] = ty;
	}
	k = read ();
	for (int i = 1;i <= k;++i) a[i] = read ();
	dfs (1,0);
	a[0] = pw[0] = 1;
	for (int i = 1;i <= k;++i) pw[i] = 	pw[i - 1] * 2 % MOD;
	for (int i = 1;i <= k;++i)
	{
		int lca = getLCA (a[i],a[i - 1]);
		++up[a[i - 1]];++down[a[i]];//up 加的位置为起点,down 加的位置为终点 
		--up[lca];--down[lca];
	} 
	dfs (1,0);
	for (int u = 1;u <= n;++u)
	{
		for (auto v : ve[u]) // u -> v 时不需花费 
		{
			if (!co[{v,u}]) continue;
			//v -> u 时此时需要花费
			if (dep[u] < dep[v]) ans = (ans + pw[up[v]] - 1 + MOD) % MOD; 
			else ans = (ans + pw[down[u]] - 1 + MOD) % MOD; 
		}
	}
	printf ("%lld\n",ans);
	return 0;
}
inline int read ()
{
    int s = 0;int f = 1;
    char ch = getchar ();
    while ((ch < '0' || ch > '9') && ch != EOF)
	{
        if (ch == '-') f = -1;
        ch = getchar ();
    }
    while (ch >= '0' && ch <= '9')
	{
        s = s * 10 + ch - '0';
        ch = getchar ();
    }
    return s * f;
}
void dfs (int u,int fa)
{
	f[u][0] = fa;dep[u] = dep[fa] + 1;
	for (int i = 1;i <= 18;++i) f[u][i] = f[f[u][i - 1]][i - 1];
	for (auto v : ve[u])
	{
		if (v == fa) continue;
		dfs (v,u);
		up[u] += up[v];down[u] += down[v];//第二次 dfs 时才用到 (通过差分数组进行还原)    第一次 dfs 时值均为 0,所以没有影响
	}
}
int getLCA (int x,int y)
{
	if (dep[x] < dep[y]) swap (x,y);
	for (int i = 18;~i;--i)
	{
		if (dep[f[x][i]] >= dep[y]) x = f[x][i];
		if (x == y) return x;
	}
	for (int i = 18;~i;--i)
		if (f[x][i] != f[y][i]) x = f[x][i],y = f[y][i];
	return f[x][0];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值