P4316 绿豆蛙的归宿,P1850 [NOIP2016 提高组] 换教室,1437D - Minimal Height Tree

104 阅读1分钟

P4316 绿豆蛙的归宿 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 概率

dp[i]表示点i到终点的期望距离,因为只知道终点的期望所以往前推,假设知道了点u的期望,点v有一条指向点u的边,那么dp[v]+=(dp[u]+dis[v][u])/siz[v],siz[v]就是v指向了多少个点,siz[z]可以预处理出来,为了更好的搜索所以建一个反图,然后从点n开始跑就行了,注意的一点就是当点v指向的点都算出期望了,才能将v加入队列,有点拓扑排序的味道

ll siz[100005],n,m;
double dp[100005];
ll cnt,head[200005];
struct Edge{
    ll from,next,to;
    double dis;
}edge[200005];
void addedge(ll from,ll to,double dis){
    edge[++cnt].to=to;
    edge[cnt].next=head[from];
    edge[cnt].dis=dis;
    head[from]=cnt;
}
ll vis[100005],out[100005];
void bfs(){
    queue<ll>q;q.push(n);dp[n]=0;
    while(!q.empty()){
        ll u=q.front();q.pop();
        for(int i=head[u];i;i=edge[i].next){
            ll j=edge[i].to;
            dp[j]+=(dp[u]+edge[i].dis)/siz[j];
            //cout<<j<<" "<<dp[j]<<" "<<u<<" "<<dp[u]<<" "<<edge[i].dis<<" "<<siz[j]<<endl;
            out[j]--;
            if(out[j]<=0) q.push(j);
        }
    }
    printf("%.2lf\n",dp[1]);
}
int main(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++) siz[i]=0;
    for(int i=1;i<=m;i++){
        ll u,vv;
        double w;
        scanf("%lld%lld%lf",&u,&vv,&w);
        siz[u]++;
        out[u]++;
        addedge(vv,u,w);
    }
    bfs();
    return 0;
}

P1850 [NOIP2016 提高组] 换教室 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 概率dp

dp[i][j][0/1]表示已经申请了j个第i个时间段有没有发送申请,转移方程有点小坑

dp[i][j][0]=min(dp[i][j][0],dp[i-1][j][0]+g[c[i-1]][c[i]]);
        dp[i][j][0]=min(dp[i][j][0],dp[i-1][j][1]+g[c[i-1]][c[i]]*(1-p[i-1])+g[d[i-1]][c[i]]*p[i-1]);
        if(j!=0){
            dp[i][j][1]=min(dp[i][j][1],dp[i-1][j-1][0]+g[c[i-1]][c[i]]*(1-p[i])+g[c[i-1]][d[i]]*p[i]);
            dp[i][j][1]=min(dp[i][j][1],dp[i-1][j-1][1]+g[c[i-1]][c[i]]*(1-p[i-1])*(1-p[i])+g[d[i-1]][c[i]]*p[i-1]*(1-p[i])+g[c[i-1]][d[i]]*(1-p[i-1])*p[i]+g[d[i-1]][d[i]]*p[i-1]*p[i]);
        }

可以看到dp[i-1][j][1]是有两段的,一个是代表申请成功了,一个是申请没有成功,因为成功是有概率的,只要注意了这个就没啥难的了,虽然自己调代码跳了半天,,,

题解 P1850 【换教室】 - 笨蛋花的小窝qwq - 洛谷博客

ll c[2005],d[2005],n,m,v,e;
double p[2005],dp[2005][2005][2],g[305][305];
int main(){
    scanf("%lld%lld%lld%lld",&n,&m,&v,&e);
    for(int i=1;i<=v;i++)
        for(int j=1;j<=v;j++)
        if(i==j) g[i][j]=0;
        else g[i][j]=inf;
    for(int i=1;i<=n;i++) scanf("%lld",&c[i]);
    for(int i=1;i<=n;i++) scanf("%lld",&d[i]);
    for(int i=1;i<=n;i++) scanf("%lf",&p[i]);
    for(int i=1;i<=e;i++){
        ll u,vv;
        double w;
        scanf("%lld%lld%lf",&u,&vv,&w);
        g[u][vv]=g[vv][u]=min(g[u][vv],w);
    }
    for(int k=1;k<=v;k++)
        for(int i=1;i<=v;i++)
        for(int j=1;j<=v;j++)
        g[i][j]=min(g[i][k]+g[k][j],g[i][j]);
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
        dp[i][j][0]=dp[i][j][1]=inf;
    dp[1][0][0]=dp[1][1][1]=0;
    for(ll j=0;j<=m;j++)
    for(ll i=j;i<=n;i++){
        dp[i][j][0]=min(dp[i][j][0],dp[i-1][j][0]+g[c[i-1]][c[i]]);
        dp[i][j][0]=min(dp[i][j][0],dp[i-1][j][1]+g[c[i-1]][c[i]]*(1-p[i-1])+g[d[i-1]][c[i]]*p[i-1]);
        if(j!=0){
            dp[i][j][1]=min(dp[i][j][1],dp[i-1][j-1][0]+g[c[i-1]][c[i]]*(1-p[i])+g[c[i-1]][d[i]]*p[i]);
            dp[i][j][1]=min(dp[i][j][1],dp[i-1][j-1][1]+g[c[i-1]][c[i]]*(1-p[i-1])*(1-p[i])+g[d[i-1]][c[i]]*p[i-1]*(1-p[i])+g[c[i-1]][d[i]]*(1-p[i-1])*p[i]+g[d[i-1]][d[i]]*p[i-1]*p[i]);
        }
    }
    double ans=1e18;
    for(int i=0;i<=m;i++) ans=min({ans,dp[n][i][0],dp[n][i][1]});
    printf("%.2lf\n",ans);
    return 0;
}

1437D - Minimal Height Tree

题目给的也就是层序遍历树,每个点的孩子编号都是升序的,那我们就记录一下上一层有多少个点,遍历该层时如果a[i]>a[i+1]那么就转到下一个点,没有下一个点就只能转到下一层,las表示上一层有多少点,cur表示这一层有多少点,ch表示已经占用的上一层的点的个数

ll t,n,a[200005];
int main(){
    scanf("%lld",&t);
    while(t--){
        scanf("%lld",&n);
        for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
        ll ans=1,las=1,ch=1,cur=1;
        for(int i=2;i<n;i++)
            if(a[i]>a[i+1]){
                ch++;
                if(ch>las){las=cur,ch=1,cur=1;ans++;}
                else cur++;
            }
            else cur++;
        printf("%lld\n",ans);
    }
    return 0;
}