DTOJ#5215. 第二题

传送门

给定一棵 N N N 个结点的树,定义一次操作如下:选择一个非根结点 x x x,其父亲为 y y y,则该操作将删除点 y y y,并且将 x x x 的父亲设置为 y y y 原本的父亲,并将原本 y y y 的其余儿子的父亲设置为 x x x

现在结点 1 ⋯ N 1 \cdots N 1N 将被依次染成黑色。假设结点 1 ⋯ K 1 \cdots K 1K 被染为黑色,你需要对所有点求出 f i f_i fi,表示在经过任意多次操作后,从点 i i i 出发向上连续最多有几个黑点(点𝑖为白色时 f i f_i fi = 0)。记 s K s_K sK 为此时所有 f i f_i fi 的和,你需要求出 s 1 × s 2 × ⋯ × s N s_1 \times s_2 \times \cdots \times s_N s1×s2××sN,对 1 0 9 + 7 10^9 + 7 109+7 取模。

注意:对于每个 f i f_i fi 的计算是独立的。

第一行一个正整数 N N N

接下来 N N N 行,每行一个数 f a [ i ] fa[i] fa[i] f a [ i ] = 0 fa[i] = 0 fa[i]=0 的点是根结点。

一个数表示答案。

样例输入 1
6
4
3
4
0
5
3
样例输出 1
13440
样例输入 2
12
7
8
2
3
6
2
2
0
5
3
4
1
样例输出 2
416049631
T T T N ≤ N\leq N特殊约定
1 , 2 , 3 1,2,3 1,2,3 100 100 100
4 , 5 , 6 4,5,6 4,5,6 800000 800000 800000树是一条链
7 , 8 , 9 , 10 7,8,9,10 7,8,9,10 800000 800000 800000

我们发现答案即为每个祖先节点除了当前链的子树中是否有染色节点,是的话贡献为1的总和的点的总和。
修改只需要暴力跳父亲即可。用树状数组维护。
不难。。

#include<bits/stdc++.h>
#define N 800005
using namespace std;
const int mod=1e9+7;
int read(){
	int op=1,sum=0;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') op=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') sum=(sum<<3)+(sum<<1)+ch-'0',ch=getchar();
	return op*sum;
}
int dfs_num,dfn[N],sz[N],rot,fa[N];
vector<int> to[N];
void dfs(int x,int las){
	sz[x]=1;
	dfn[x]=++dfs_num;
	for(int i=0;i<to[x].size();++i){
		int y=to[x][i];
		if(y==las)continue;
		dfs(y,x);
		sz[x]+=sz[y];
	}
}
inline void add(int &x,int y){x=(x+y>=mod)?x+y-mod:x+y;}
struct BIT{
	int t[N];
	inline void init(){
		memset(t,0,sizeof(t));
	}
	inline int lowbit(int x){return x&(-x);}
	inline int get(int x){
		int now=0;
		for(;x;x-=lowbit(x))add(now,t[x]);
		return now;
	}
	inline void ch(int x,int val){
		for(;x<=dfs_num;x+=lowbit(x))add(t[x],val);
	}
}T[2];
inline int ct(int x){
	return T[1].get(dfn[x]+sz[x]-1)-T[1].get(dfn[x]-1);
}
int main(){
	int n=read();
	for(int i=1;i<=n;++i){
		int x=read();fa[i]=x;
		if(!x)rot=i;
		else to[x].push_back(i),to[i].push_back(x);
	}
	dfs(rot,0);
	int ans=0,sum=1;
	T[0].init();T[1].init();
	for(int i=1;i<=n;++i){
		int x=i;
		add(ans,T[0].get(dfn[i])+1);
		while(x!=rot&&ct(x)==0){
			if(fa[x]<=i)break;
			int cs=ct(fa[x])-ct(x);
			for(int j=0;j<to[fa[x]].size();++j){
				int y=to[fa[x]][j];
				if(y==fa[fa[x]]||y==x)continue;
				if(ct(y)==cs){
					T[0].ch(dfn[y],1);T[0].ch(dfn[y]+sz[y],-1);
					add(ans,ct(y));
				}
			}
			x=fa[x];
		}
		x=i;
		for(int j=0;j<to[x].size();++j){
			int y=to[x][j];
			if(y==fa[i])continue;
			if(ct(y)==ct(x)){
				T[0].ch(dfn[y],1);T[0].ch(dfn[y]+sz[y],-1);
				add(ans,ct(y));
			}
		}
	    T[1].ch(dfn[i],1);
	    sum=1ll*sum*ans%mod;
	}
	printf("%d\n",(sum+mod)%mod);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值