Gym102055 (CCPC-final 2018)

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=1naiwi

分类讨论+dp之类的状态转移狂写

首先
我们注意到路径里会有1->2->3->2->1这样子的一个回路,我们可以预处理出每个点向下走会有多大概率出现这样的回路
我们发现可能的路径有两种情况:

  1. 终点是叶子结点,到达叶子结点的时候上一个结点已经经过了两次
  2. 终点是非叶子结点,如下图这样最后以一条回路结尾,并且终点的上方结点已经经过了两次
    1->4->1->2->3->4->3->2(情况2)

我们可以从上往下dp,dfs直接传状态进去做。
这里我的做法非常的繁琐.
考虑到我们需要维护当前节点的上方是否可以经过,所以我们可以维护出dp[0/1],其中转移的时候需要特别注意预处理的回路长度为1的时候(1->2->1),我们统计贡献需要分别处理。

还有一种特殊情况是走下去之前有一个长度为1的回路
1->2->3->2->3->4

我们统计的时候需要注意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] [1i]必须要在 [ 1 − j ] [1-j] [1j]前面 ( i < j ) (i<j) (i<j)
问能得到的最小字典序的序列

如果仔细分析的话问题等价于每次添加一个序列b,在尾部添加b或者在头部添加倒着的b

哈希二分即可,复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
注意如果不能翻转 [ 1 − n ] [1-n] [1n]的话直接把最后一段拉出来就可以了(因为他一定出现在答案的末尾)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值