安全路径 [容斥]

安 全 路 径 安全路径


最 初 想 法 \color{blue}{最初想法}

感觉是树形dp, 一步错步步错 .
F [ i ] F[i] F[i] 表示以 i i i 点端点在其子树中有多少条符合条件的路径, 且当前枚举到了子树节点 t o to to,

发现这样的状态只能 O ( N 2 ) O(N^2) O(N2) 枚举根才能得出正确的答案 .

  • 若边权为 ′ b ′ 'b' b, 则 A n s + = F [ k ] ∗ F [ t o ] Ans+=F[k]*F[to] Ans+=F[k]F[to], F [ k ] + = F [ t o ] F[k] += F[to] F[k]+=F[to]
  • 若边权为 ′ r ′ 'r' r, 则 A n s + = F [ k ] ∗ s i z e [ t o ] Ans += F[k]*size[to] Ans+=F[k]size[to], F [ k ] + = s i z e [ t o ] F[k] += size[to] F[k]+=size[to].

然后就交上去了, 发现爆炸 .
原因是 题目中的三元组 之间的路径 才有限制(死在这里), 不一定成一条链, 可以是下图这个样子,


所以这个算法已废 .

样例真水


正 解 部 分 \color{red}{正解部分}

可以容斥, 求出 危险的三元组 的个数,
危险的三元组点与点之间 存在 ′ b ′ 'b' b 路径,
所以求出所有蓝色的连通块大小 S i S_i Si,
A n s = C N 3 − ∑ C S i 3 − ∑ C S i 2 ∗ ( N − S i ) Ans = C_{N}^3 - \sum C_{S_i}^{3} - \sum C_{S_i}^2*(N-S_i) Ans=CN3CSi3CSi2(NSi) .

十年OI一场空, 看错题目见祖宗 …


实 现 部 分 \color{red}{实现部分}

若两个点有蓝边相连, 即可归入同一个连通块, 可以使用并查集实现 .

#include<bits/stdc++.h>
#define reg register

int read(){
        char c;
        int s = 0, flag = 1;
        while((c=getchar()) && !isdigit(c))
                if(c == '-'){ flag = -1, c = getchar(); break ; }
        while(isdigit(c)) s = s*10 + c-'0', c = getchar();
        return s * flag;
}

const int maxn = 50005;
const int mod = 1e9 + 7;

int N;
int F[maxn];
int fac[maxn];
int size[maxn];
int rev[maxn];

int Ksm(int a, int b){
	int s = 1;
	while(b){
		if(b & 1) s = 1ll*s*a % mod;
		a = 1ll*a*a % mod;
		b >>= 1;
	}
	return s;
}

int C(int n, int m){
        int t1 = fac[n];
        int t2 = 1ll*fac[n-m]*fac[m] % mod;
        return 1ll*t1*Ksm(t2, mod-2) % mod;
}

int Find(int x){ return F[x]==x?x:F[x]=Find(F[x]); }

int main(){
        N = read();
        for(reg int i = 1; i <= N; i ++) F[i] = i, size[i] = 1;
        for(reg int i = 1; i < N; i ++){
                char ch[2];
                int a = read(), b = read();
                scanf("%s", ch);
                if(ch[0] == 'b'){
                        int t1 = Find(a), t2 = Find(b);
                        if(t1 != t2) F[t2] = t1, size[t1] += size[t2];
                }
        }
        fac[0] = 1;
        for(reg int i = 1; i <= N; i ++) fac[i] = 1ll*fac[i-1]*i % mod;
        int Ans = C(N, 3);
        for(reg int i = 1; i <= N; i ++){
                if(Find(i) != i) continue ;
                if(size[i] >= 3) Ans = (1ll*Ans - C(size[i], 3) + mod) % mod;
                if(size[i] >= 2) Ans = (1ll*Ans - (1ll*C(size[i], 2)*(N-size[i])%mod) + mod) % mod;
        }
        printf("%d\n", Ans);
        return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值