ABC220

D

状态机DP

每次把左侧两个元素取出来进行 ( x + y ) m o d    10 (x+y)\mod10 (x+y)mod10 ( x ∗ y ) m o d    10 (x*y)\mod 10 (xy)mod10,再插入左侧,问最后剩下的一个元素,余数分别是 0 − 9 0-9 09的方案数

注意到每次操作只关心左侧第一个和第二个,第一个是前面操作得到的,第二个是未被操作的原数组,因此我们直接dp,用一个维度记录左侧第一个的值,就是无后效性的。

具体来说 d p ( i , j ) dp(i,j) dp(i,j)表示操作完前 i i i个元素,左侧第一个元素是 j j j的方案数,最后答案就是 d p ( n , 0 − 9 ) dp(n,0-9) dp(n,09)

void solve(){
	int n;
	cin>>n;
	vi a(n+1);
	rep(i,1,n){
		cin>>a[i];
	}
	
	vvi dp(n+1,vi(10));
	dp[2][(a[1]+a[2])%10]++;
	dp[2][a[1]*a[2]%10]++;
	
	rep(i,3,n){
		rep(j,0,9){
			int k=(j+a[i])%10;
			dp[i][k]+=dp[i-1][j];
			dp[i][k]%=M2;
			
			k=j*a[i]%10;
			dp[i][k]+=dp[i-1][j];
			dp[i][k]%=M2;
		}
	}
	rep(i,0,9){
		cout<<dp[n][i]<<'\n';
	}
}

E

树上路径计数

给一个完全二叉树,求长度为 d d d的路径总数。

首先对于路径问题,可以枚举子树根节点,随着往下走,深度减小,根节点数倍增。

对于一个根节点,长度为 d d d的路径,可以左子树选一个长度为 i i i的路径,右子树选一个长度为 d − i d-i di的路径,注意到这是边权为 1 1 1的,所以从根节点出发,长度为 i i i的路径个数,就是该子树里深度为 i i i的点数。

那么对于一个根节点,答案就是 ∑ i = 0 d c n t 1 ( i ) ∗ c n t 2 ( d − i ) \sum_{i=0}^{d}{cnt_1(i)*cnt_2(d-i)} i=0dcnt1(i)cnt2(di), c n t 1 / 2 ( i ) cnt_{1/2}(i) cnt1/2(i)分别表示左右子树深度为 i i i的点数

注意到这是完全二叉树,所以 c n t ( i ) = 2 i − 1 cnt(i)=2^{i-1} cnt(i)=2i1,所以 c n t ( i ) ∗ c n t ( d − i ) = 2 d − 2 cnt(i)*cnt(d-i)=2^{d-2} cnt(i)cnt(di)=2d2

这是因为深度为 i i i的点是左右子树均分的,但对于 i = 0 i=0 i=0的情况,左右是共用一个, c n t ( i ) = 2 i cnt(i)=2^i cnt(i)=2i,因此 i = 0 , i = d i=0,i=d i=0,i=d的情况需要单独计算

并且随着根节点往下走,子树深度逐渐变小,可能不是深度 [ 0 , d ] [0,d] [0,d]的点都存在。具体来说,设当前深度为 d e p dep dep,需要满足
0 < = i < = d e p , 0 < = d − i < = d e p 0<=i<=dep,0<=d-i<=dep 0<=i<=dep,0<=di<=dep

也就是
m a x ( 0 , d − d e p ) < = i < = m i n ( d e p , d ) max(0,d-dep)<=i<=min(dep,d) max(0,ddep)<=i<=min(dep,d)

所以前面的求和需要改成
∑ i = m a x ( 0 , d e p − d ) m i n ( d e p , d ) c n t 1 ( i ) ∗ c n t 2 ( d − i ) \sum_{i=max(0,dep-d)}^{min(dep,d)}{cnt_1(i)*cnt_2(d-i)} i=max(0,depd)min(dep,d)cnt1(i)cnt2(di)

void solve(){
	int n,d;
	cin>>n>>d;
	
	int ans=0;
	int p=1;
	int dep=n-1;
	
	int pw=power(2,d,M2);
	int inv2=inv(2,M2);
	int inv4=inv(4,M2);
	while(dep){
		int l=max(d-dep,0ll);
		int r=min(dep,d);
		if(l>r)break;
		
		int len=r-l+1;
		int cur=0;
		if(l==0){
			len--;
			cur+=pw*inv2%M2;
			cur%=M2;
		}
		if(r==d){
			len--;
			cur+=pw*inv2%M2;
			cur%=M2;
		}
		cur+=len*pw%M2*inv4%M2;
		cur%=M2;
		ans+=cur*p%M2;
		ans%=M2;
		p=p*2%M2;
		
//		cout<<cur<<'\n';
		
		dep--;
	}
	cout<<ans*2%M2;
}

F

换根dp

对于树上每个点,求出所有点到这个点距离之和

典,首先可以一个树形dp求出对于一个点为根的答案。然后根从一个点 u u u换到它的儿子 v v v时, v v v子树里所有点距离都减少1,子树外所有点距离都加1,因此答案增加 − s z v + ( n − s z v ) -sz_v+(n-sz_v) szv+(nszv)

void solve(){
	int n;
	cin>>n;
	vvi g(n+1);
	rep(i,2,n){
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	
	vi sz(n+1,1),dp0(n+1);
	auto &&dfs=[&](auto &&dfs,int u,int f)->void{
		for(int v:g[u]){
			if(v==f)continue;
			dfs(dfs,v,u);
			sz[u]+=sz[v];
			dp0[u]+=dp0[v]+sz[v];
		}
	};
	dfs(dfs,1,-1);
	vi dp(n+1);
	dp[1]=dp0[1];
	
	auto &&dfs1=[&](auto &&dfs1,int u,int f)->void{
		for(int v:g[u]){
			if(v==f)continue;
			dp[v]=dp[u]-sz[v]+(n-sz[v]);
			dfs1(dfs1,v,u);
		}
	};
	dfs1(dfs1,1,-1);
	
	rep(i,1,n){
		cout<<dp[i]<<'\n';
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值