D
状态机DP
每次把左侧两个元素取出来进行 ( x + y ) m o d 10 (x+y)\mod10 (x+y)mod10或 ( x ∗ y ) m o d 10 (x*y)\mod 10 (x∗y)mod10,再插入左侧,问最后剩下的一个元素,余数分别是 0 − 9 0-9 0−9的方案数
注意到每次操作只关心左侧第一个和第二个,第一个是前面操作得到的,第二个是未被操作的原数组,因此我们直接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,0−9)
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 d−i的路径,注意到这是边权为 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=0∑dcnt1(i)∗cnt2(d−i), 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)=2i−1,所以 c n t ( i ) ∗ c n t ( d − i ) = 2 d − 2 cnt(i)*cnt(d-i)=2^{d-2} cnt(i)∗cnt(d−i)=2d−2
这是因为深度为 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<=d−i<=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,d−dep)<=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,dep−d)∑min(dep,d)cnt1(i)∗cnt2(d−i)
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+(n−szv)
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';
}
}