D Cube
给定一颗有根树,Panda在树上行走,起点为根节点,每次Panda都会沿着边从一个点走到另一个点。
如果一个节点经过两次,那么这个节点就不能再经过了。
Panda每次等概率的从剩下所有可以经过的相邻点选择一个点前进,当所有相邻点都不能走后panda就会停止
询问每个点的成为终点的概率
w
i
w_i
wi
其中输入会给你一个
a
i
a_i
ai 你需要输出
∑
i
=
1
n
a
i
∗
w
i
\sum^{n}_{i=1}a_i*w_i
∑i=1nai∗wi
分类讨论+dp之类的状态转移狂写
首先
我们注意到路径里会有1->2->3->2->1这样子的一个回路,我们可以预处理出每个点向下走会有多大概率出现这样的回路
我们发现可能的路径有两种情况:
- 终点是叶子结点,到达叶子结点的时候上一个结点已经经过了两次
- 终点是非叶子结点,如下图这样最后以一条回路结尾,并且终点的上方结点已经经过了两次
我们可以从上往下dp,dfs直接传状态进去做。
这里我的做法非常的繁琐.
考虑到我们需要维护当前节点的上方是否可以经过,所以我们可以维护出dp[0/1],其中转移的时候需要特别注意预处理的回路长度为1的时候(1->2->1),我们统计贡献需要分别处理。
还有一种特殊情况是走下去之前有一个长度为1的回路
我们统计的时候需要注意3这个位置就不能再统计回路的贡献,需要分别处理,非常恶心的情况
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1e5+10;
const int mod=1e9+7;
int inv[maxn];
void init(){
inv[0]=1;
inv[1]=1;
for(int i=2;i<maxn;i++){
inv[i]=(int)(mod-mod/i)*inv[mod%i]%mod;
}
}
vector<int> ve[maxn];
int dp[maxn],a[maxn],once[maxn];//dp到达当前点走下去走上来出现一条回路的概率
int ans=0,root;
void add(int &x,int y){
if((x+=y)>=mod)x-=mod;
}
void del(int &x,int y){
if((x-=y)<0)x+=mod;
}
int dfs1(int x,int h){
once[x]=inv[(int)ve[x].size()];
int res=0;
int noh=(int)ve[x].size();
if(x!=root)noh--;
int siz=(int)ve[x].size();
if(x!=root)res+=once[x];
for(auto it:ve[x])if(it!=h){
int z=dfs1(it,x);
del(z,once[it]);
add(res,z*inv[siz-1]%mod*inv[siz]%mod);
add(res,once[it]*inv[siz]%mod*inv[siz]%mod);
}
dp[x]=res;
if(test)cout<<"dfs1 x="<<x<<" h="<<h<<" dp["<<x<<"]="<<dp[x]<<" once["<<x<<"]="<<once[x]<<"\n";
return res;
}
/*
in2 包括 in3
*/
void dfs2(int x,int h,int in1,int in2,int in3){//in1表示下去还有上来的可能但是我们必须不算,in2表示没有上去的可能了,in3表示上一轮有长度为1的回路
if(ve[x].size()==1&&ve[x].front()==h){
add(ans,a[x]*in2%mod);
return;
}
int noh=(int)ve[x].size();
if(x!=root)noh--;
int siz=(int)ve[x].size();
int Sum1=0;//in1下来的下去之后少掉一条路径的可能
int Sum2=0;//in2下来的下去之后少掉一条路径的可能
int Sum3=0;//in1下来的下去之后没有少掉一条路径的可能
int Sum4=0;//in2下来的下去之后没有少掉一条路径的可能
int gl1=inv[siz];//可以走上去的概率
int gl2=inv[noh];//不能走上去的概率
for(auto it:ve[x])if(it!=h){
add(Sum1,(dp[it]-once[it]+mod)%mod*inv[siz]%mod*in1%mod);
add(Sum3,once[it]*inv[siz]%mod*in1%mod);
add(Sum2,(dp[it]-once[it]+mod)%mod*inv[noh]%mod*(in2-in3+mod)%mod);
add(Sum4,once[it]*inv[noh]%mod*(in2-in3+mod)%mod);
if(noh==1){
int g=(in2-in3+mod)*((dp[it]-once[it]+mod)%mod)%mod;
add(ans,g*a[x]%mod);
}
}
for(auto it:ve[x])if(it!=h){
del(Sum1,(dp[it]-once[it]+mod)%mod*inv[siz]%mod*in1%mod);
del(Sum2,(dp[it]-once[it]+mod)%mod*inv[noh]%mod*(in2-in3+mod)%mod);
del(Sum3,once[it]*inv[siz]%mod*in1%mod);
del(Sum4,once[it]*inv[noh]%mod*(in2-in3+mod)%mod);
int In1=0,In2=0,In3=0;
add(In1,(in1*gl1%mod+(in2-in3+mod)%mod*gl2%mod)%mod);
add(In2,Sum1*inv[siz-1]%mod);
add(In2,Sum2*inv[noh-1]%mod);
add(In2,Sum3*inv[siz]%mod);
add(In2,Sum4*inv[noh]%mod);
add(In2,in3*inv[noh]%mod);
add(In2,inv[siz]*once[it]%mod*inv[siz]%mod*in1%mod);
add(In3,inv[siz]*once[it]%mod*inv[siz]%mod*in1%mod);
add(In2,inv[noh]*once[it]%mod*inv[noh]%mod*(in2-in3+mod)%mod);
add(In3,inv[noh]*once[it]%mod*inv[noh]%mod*(in2-in3+mod)%mod);
dfs2(it,x,In1,In2,In3);
add(Sum1,(dp[it]-once[it]+mod)%mod*inv[siz]%mod*in1%mod);
add(Sum2,(dp[it]-once[it]+mod)%mod*inv[noh]%mod*(in2-in3+mod)%mod);
add(Sum3,once[it]*inv[siz]%mod*in1%mod);
add(Sum4,once[it]*inv[noh]%mod*(in2-in3+mod)%mod);
}
}
int solve()
{
int n;
cin>>n;
ans=0;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)ve[i].clear(),once[i]=dp[i]=0;
for(int i=1;i<n;i++){
int x,y;cin>>x>>y;
ve[x].push_back(y);
ve[y].push_back(x);
}
cin>>root;
dfs1(root,-1);
dfs2(root,-1,0,1,0);
return ans;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
init();
int T;
cin>>T;
for(int ts=1;ts<=T;ts++){
cout<<"Case "<<ts<<": "<<solve()<<"\n";
}
return 0;
}
J Mr. Panda and Sequence Puzzle
给你一个序列,告诉你可以翻转一些位置的前缀,其中翻转前缀
[
1
−
i
]
[1-i]
[1−i]必须要在
[
1
−
j
]
[1-j]
[1−j]前面
(
i
<
j
)
(i<j)
(i<j)
问能得到的最小字典序的序列
如果仔细分析的话问题等价于每次添加一个序列b,在尾部添加b或者在头部添加倒着的b
哈希二分即可,复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
注意如果不能翻转
[
1
−
n
]
[1-n]
[1−n]的话直接把最后一段拉出来就可以了(因为他一定出现在答案的末尾)