bzoj 4899 记忆的轮廓 题解(概率dp+决策单调性优化)

题目背景

四次死亡轮回后,昴终于到达了贤者之塔,当代贤者夏乌拉一见到昴就上前抱住了昴“师傅!你终于回来了!你有着和师傅一样的魔女的余香,肯定是师傅”。
众所周知,大贤者是嫉妒魔女沙提拉的老公,400年前与神龙、剑圣一起封印魔女因子暴走的莎缇拉。在魔女茶会的时候,莎缇拉也表示过对昴浓浓的爱意,昴便是被莎缇拉召唤来异世界的。
而贤者之塔中的资料与试炼,似乎都指向同一种可能性……记忆的轮廓,逐渐显形……

题目描述

通往贤者之塔的路上,有许多的危机。
我们可以把这个地形看做是一颗树,根节点编号为1,目标节点编号为n,其中1-n的简单路径上,编号依次递增,在[1,n]中,一共有n个节点。
我们把编号在[1,n]的叫做正确节点,[n+1,m]的叫做错误节点。一个叶子,如果是正确节点则为正确叶子,否则称为错误叶子。
莎缇拉要帮助昴到达贤者之塔,因此现在面临着存档位置设定的问题。为了让昴成长为英雄,因此一共只有p次存档的机会,其中1和n必须存档。被莎缇拉设置为要存档的节点称为存档位置。
当然不能让昴陷入死循环,所以存档只能在正确节点上进行,而且同一个节点不能存多次档。因为通往贤者之塔的路上有影响的瘴气,因此莎缇拉假设昴每次位于树上一个节点时,都会等概率选择一个儿子走下去。每当走到一个错误叶子时,再走一步就会读档。
具体的,每次昴到达一个新的存档位置,存档点便会更新为这个位置(假如现在的存档点是i,现在走到了一个存档位置j>i,那么存档点便会更新为j)。读档的意思就是回到当前存档点。
初始昴位于1,当昴走到正确叶子n时,便结束了路程。莎缇拉想知道,最优情况下,昴结束路程的期望步数是多少?
输入格式

第一行一个正整数T表示数据组数。
接下来每组数据,首先读入三个正整数n,m,p。
接下来m-n行,描述树上所有的非正确边(正确边即连接两个正确节点的边),用两个正整数j,k表示j与k之间有一条连边,j和k可以均为错误节点,也可以一个为正确节点另一个为错误节点。数据保证j是k的父亲。
输出格式

T行每行一个实数表示每组数据的答案。请保留四位小数。

样例输入

1
3 7 2
1 4
2 5
3 6
3 7

样例输出

9.000

数据范围及约定

50%,n=p
70%,50<=p<=n<=500
100%,50<=p<=n<=700,m<=1500,T<=5
数据保证每个除了n的正确节点均有至少2个儿子,至多3个儿子。

---------------------------------------------------------------分界线---------------------------------------------------------------

考试T2,调考前刚qj过改过,确实是一道毒瘤题好题,考试时时间不够看都没看考完试才开始做了这题。

理解题理解了一节课

做题先看数据范围,否则凉凉。

我们可以看到有50%的数据是n=p的,对于n=p的情况,我们不难分析出每个点都存档是最优解,这样情况就简单很多。

接下来我们考虑怎么转移。

设个g[i]为对于一个错误节点i还要走多少步会存档。

g[i]=1+∑g[j]/du[i](j是i的儿子)。一遍dfs就可以处理出来g数组。

我们再处理数组sum,sum[i]=∑g[j](j是i的错误儿子)。

设f[i]表示正确节点i走到n的期望步数,显然f[n]=0,我们倒着递推。
f[i]=1+1/d[i]*f[i+1]+1/d[i]*sigma{g[j]+f[i]}[j是i的错误儿子]
移项得f[i]=d[i]+f[i+1]+s[i]。

over,50pts到手。

接下来我们考虑把它优化到70pts。

设dp(i,j)表示存档点在i还有j次存档机会的最优解。

设a(i,j)表示存档点在i,从i走到正确节点j的最少期望步数。

首先我们可以o(n2)把a数组处理出来。

a(i,j)=a(i,j-1)+1+1/du(j-1)×∑(a(i,j)+g(k)){k是j-1的错误儿子}。

整理移项得a(i,j)=du(i,j-1)×a(i,j-1)+sum(j-1)+du(j-1)。

然后我们枚举存档点k,则dp(i,j)可以由dp(k,j-1)和a(i,k)转移。

时间复杂度O(n2p),70pts到手。

最后我们来考虑正解。其实博主并不会正解。

还是放直链吧。%%%出题人。

https://blog.csdn.net/WerKeyTom_FTD/article/details/53026266

出题人给出了三种正解。

由于第二种看起来十分好写比较优秀,博主选择了第二种。

到现在博主还是很mengbi,在这里就不给予讲解了。

如果有时间的话博主也会用其他两种方法A掉这题的。

下面是三个分数段的代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
const int N=3005;
using namespace std;
int first[N],nex[N],to[N],tot,vis[N],du[N];double sum[N],g[N],f[N];
void add(int a,int b){
    to[++tot]=b;nex[tot]=first[a];first[a]=tot;
}
void dfs(int x){
    g[x]=1.0;vis[x]=1;
    for(int i=first[x];i;i=nex[i]){
        int y=to[i];
        dfs(y);
        g[x]+=1.0/du[x]*g[y];
    }
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        memset(du,0,sizeof(du));
        //memset(sum,0,sizeof(sum));
        memset(g,0,sizeof(g));tot=0;
        int n,m,p;
        scanf("%d%d%d",&n,&m,&p);
        for(int i=1;i<=m-n;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add(a,b);
            du[a]++;
        }
        for(int i=1;i<=n;i++) du[i]++;
        for(int i=n+1;i<=m;i++){
            if(vis[i]) continue;
            dfs(i);
        }
        for(int i=1;i<=n;i++){
            sum[i]=0.0;
            for(int j=first[i];j;j=nex[j]){
                //if(j>n&&j<=m)
                if(to[j]>n&&to[j]<=m)
                sum[i]+=g[to[j]];
            }
        }
        f[n]=0.0;
        for(int i=n-1;i>=1;i--){
            f[i]=f[i+1]+sum[i]+du[i];
            //cout<<g[i]<<" ";
        }
        //for(int i=n+1;i<=m;i++) /*cout<<i<<" ",*/printf("%.4lf ",g[i]);
        printf("%.4lf\n",f[1]);
    }
}

50pts

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
#define R register
inline int read(){
    R int aa=0,bb=1;char cc=getchar();
    while(cc<‘0‘||cc>‘9‘)
        {if(cc==‘-‘)bb=-1;cc=getchar();}
    while(cc<=‘9‘&&cc>=‘0‘)
        {aa=aa*10+cc-‘0‘;cc=getchar();}
    return aa*bb;
}
const int N=703;
const int M=1503;
struct tree{
    int v,last;
}tr[M*2];
int tot=0,first[M],du[M];
void add(int x,int y){
    tr[++tot].v=y;
    tr[tot].last=first[x];
    first[x]=tot;
    du[x]++;
}
int T,n,m,p;
bool vi[M];
double g[M],sum[N],f[N][N],fg[N][N],fi[N];
void dfs(int x){
    if(vi[x]) return;
    g[x]=1.0; vi[x]=1;
    for(R int i=first[x],v;i;i=tr[i].last){
        v=tr[i].v;
        dfs(v);
        g[x]+=1.0/du[x]*g[v];
    }
}
int main(){
    T=read();
    while(T--){
        memset(vi,0,sizeof(vi));
        memset(du,0,sizeof(du));
        memset(first,0,sizeof(first));
        tot=0;
        n=read();m=read();p=read();
        for(R int i=1,x,y;i<=m-n;i++){
            x=read();y=read();
            add(x,y);
        }
        for(R int i=1;i<=n;i++)du[i]++;
        for(R int i=n+1;i<=m;i++) if(!vi[i]) dfs(i);
        for(R int i=1;i<=n;i++){
            sum[i]=0.0;
            for(R int j=first[i],v;j;j=tr[j].last){
                v=tr[j].v;
                sum[i]+=1.0*g[v];
            }
        }
        if(n==p){
            fi[n]=0.0;
            for(R int i=n-1;i>=1;i--)
                fi[i]=(double)(du[i]+fi[i+1]+sum[i]);
            printf("%.4lf\n",fi[1]);
            continue;
        }

        for(R int i=1;i<=n;i++){
            fg[i][i]=0.0;
            for(R int j=i+1;j<=n;j++){
                fg[i][j]=fg[i][j-1]*du[j-1]+du[j-1]+sum[j-1];
            }
        }
        for(R int i=1;i<=n;i++)
            for(R int j=0;j<=p;j++)
                f[i][j]=0x7ffffff;
        for(R int i=0;i<=p;i++)  f[n][i]=0.0;
        for(R int i=n-1;i>=1;i--){
            for(R int j=1;j<p;j++){
                for(R int k=i+1;k<=n;k++){
                    f[i][j]=min( f[k][j-1]+fg[i][k], f[i][j]);
                }
            }
        }
        printf("%.4lf\n",f[1][p-1]);
    }
    return 0;
}

70pts

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
#define R register
inline int read()
{
    R int aa=0,bb=1;char cc=getchar();
    while(cc<‘0‘||cc>‘9‘)
        {if(cc==‘-‘)bb=-1;cc=getchar();}
    while(cc<=‘9‘&&cc>=‘0‘)
        {aa=aa*10+cc-‘0‘;cc=getchar();}
    return aa*bb;
}
const int N=703;
const int M=1503;
struct tree{
    int v,last;
}tr[M*2];
int tot=0,first[M],du[M];
void add(int x,int y)
{
    tr[++tot].v=y;
    tr[tot].last=first[x];
    first[x]=tot;
    du[x]++;
}
int T,n,m,p;
bool vi[M];
double g[M],sum[N],f[N][N],fg[N][N],fi[N];
void dfs(int x)
{
    if(vi[x]) return;
    g[x]=1.0; vi[x]=1;
    for(R int i=first[x],v;i;i=tr[i].last){
        v=tr[i].v;
        dfs(v);
        g[x]+=1.0/du[x]*g[v];
    }
}
int main()
{
    T=read();
    while(T--){
        memset(vi,0,sizeof(vi));
        memset(du,0,sizeof(du));
        memset(first,0,sizeof(first));
        tot=0;
        n=read();m=read();p=read();
        for(R int i=1,x,y;i<=m-n;i++){
            x=read();y=read();
            add(x,y);
        }
        for(R int i=1;i<=n;i++)du[i]++;
        for(R int i=n+1;i<=m;i++) if(!vi[i]) dfs(i);
        for(R int i=1;i<=n;i++){
            sum[i]=0.0;
            for(R int j=first[i],v;j;j=tr[j].last){
                v=tr[j].v;
                sum[i]+=1.0*g[v];
            }
        }
        if(n==p){
            fi[n]=0.0;
            for(R int i=n-1;i>=1;i--)
                fi[i]=(double)(du[i]+fi[i+1]+sum[i]);
            printf("%.4lf\n",fi[1]);
            continue;
        }

        for(R int i=1;i<=n;i++){
            fg[i][i]=0.0;
            for(R int j=i+1;j<=n;j++){
                fg[i][j]=fg[i][j-1]*du[j-1]+du[j-1]+sum[j-1];
            }
        }
        for(R int i=1;i<=n;i++)
            for(R int j=0;j<=p;j++)
                f[i][j]=0x7ffffff;
        for(R int i=0;i<=p;i++)  f[n][i]=0.0;
        for(R int i=n-1;i>=1;i--){
            for(R int j=1;j<p;j++){
                int r=min(i+40,n);
                for(R int k=i+1;k<=r;k++){
                    f[i][j]=min( f[k][j-1]+fg[i][k], f[i][j]);
                }
            }
        }
        printf("%.4lf\n",f[1][p-1]);
    }
    return 0;
}

AC

原文地址:https://www.cnblogs.com/leom10/p/11072807.html

时间: 2024-08-29 00:52:51

bzoj 4899 记忆的轮廓 题解(概率dp+决策单调性优化)的相关文章

题解——[NOI2009]诗人小G 决策单调性优化DP

第一次写这种二分来优化决策单调性的问题.... 调了好久,,,各种细节问题 显然有DP方程: f[i]=min(f[j] + qpow(abs(sum[i] - sum[j] - L - 1))); 其中f[i]代表到了第i个句子的最小答案 qpow用于处理^p sum为前缀和 (同时为了处理句子之间的空格问题,我们在统计前缀和的时候就默认在句子后面加一个空格, 然后在计算的时候,由于每一行只有最后一个不用加空格,直接减掉这个多加的空格即可获得正确长度) 首先我们可以打表发现是满足决策单调性的,

决策单调性优化dp 专题练习

决策单调性优化dp 专题练习 优化方法总结 一.斜率优化 对于形如 \(dp[i]=dp[j]+(i-j)*(i-j)\)类型的转移方程,维护一个上凸包或者下凸包,找到切点快速求解 技法: 1.单调队列 : 在保证插入和查询的x坐标均具有单调性时可以使用 2.单调栈+二分:保证插入有单调性,不保证查询有单调性 3.分治+ 1 或 2:在每次分治时将\([l,mid]\)这段区间排序后插入,然后更新右区间\([mid+1,r]\)的答案 二.分治.单调队列维护有单调性的转移 (甚至还有分治套分治)

TYVJ1071 LCIS 线性DP+决策集优化

问题描述 TYVJ1071 题解 暴力\(\mathrm{DP}\) 首先,一个\(O(n^3)\)的解法: 设\(opt_{i,j}\)代表\(a\)的前\(i\)个和\(b\)的前\(j\)个的\(\mathrm{LCIS}\). 显然有: 1.\(a_i=b_j\) \[opt_{i,j}=opt_{i-1,j}\] 2.\(a_i≠b_j\) \[opt_{i,j}=max_{0 \le k < j,b_k<a_i} {opt_{i-1,k}}+1\] 于是得到代码: #include

Bzoj 1563: [NOI2009]诗人小G(决策单调性优化)

原题面 带有详细证明的转这里 题意:每一个线段有一个长度,有一个标准长,现在要把这些线段按照顺序分行,每行的不和谐值等于标准长和该行线段总长的差的绝对值的p次方.现在要求最小的不和谐值之和. 开始的时候完全读错题了,以为p==2 for ever.真是太天真.后来看数据范围才发现.我真是面向数据编程? n^2的dp是一眼秒的.然后如果是p=2,那就是一个非常像玩具装箱的斜率优化dp.对于4.5的数据范围.可以想到用贪心优化dp.因为每一行的长度不会通过拼接线段(线段条数>=2)达到2*标准长,这

决策单调性优化dp

决策单调性: 对于一些dp方程,经过一系列的猜想和证明,可以得出,所有取的最优解的转移点(即决策点)位置是单调递增的. 即:假设f[i]=min(f[j]+b[j]) (j<i) 并且,对于任意f[i]的决策点g[i],总有f[i+1]的决策点g[i+1]>=g[i](或者<=g[i]) 那么,这个方程就具备决策单调性. 这个有什么用吗? 不懂具体优化方法的话确实也没有什么用.可能还是n^2的.只不过范围可能少了一些. 经典入门例题: Description: [POI2011]Ligh

Codeforces 868F. Yet Another Minimization Problem【决策单调性优化DP】【分治】【莫队】

LINK 题目大意 给你一个序列分成k段 每一段的代价是满足\((a_i=a_j)\)的无序数对\((i,j)\)的个数 求最小的代价 思路 首先有一个暴力dp的思路是\(dp_{i,k}=min(dp_{j,k}+calc(j+1,i))\) 然后看看怎么优化 证明一下这个DP的决策单调性: trz说可以冥想一下是对的就可以 所以我就不证了 (其实就是决策点向左移动一定不会更优) 然后就分治记录当前的处理区间和决策区间就可以啦 //Author: dream_maker #include<bi

Gym - 101002H: Jewel Thief (背包,分组,DP决策单调性)

pro:给定N,M.输入N个物品,(si,vi)表示第i个物品体积为si,价值为vi,s<=300,vi<=1e9: N<1e6:现在要求,对于背包体积为1到M时,求出最大背包价值. sol:显然直接跑背包会爆炸. 发现物品体积都比较小,我们先对相同体积的排序,对于体积相同的一起处理. 然后发现转移都是在差为体积整数倍之间,按照容量对体积取模分组 ,发现组内部转移满足神奇的决策单调性. 然后就是s次分治. O(KClogC) #include<bits/stdc++.h> #

BZOJ 1246 &amp; 有点不一样的概率DP

题意: 题意够坑的啊... 一个色子有n个面,第k次掷出一个加上这个k.求掷出所有面的期望值. 我一直以为值是色子面上的... 那么问题来了在色子面上怎么做...n还是1w级别... SOL: 对着理解错的题面想了半天还是没想出来比O(n^2)强的算法.. 这题跟上次lightoj那道丝帛题有点像,但那个每个面都可以看做一样的所以丝帛很多... 空间也一样要炸,然后不行看题解...发现原来是这样... 感觉对着原来的问题每种情况都相当于是独立的...感觉除了撞鸭其它没法搞啊... 然而感觉改了以

bzoj 3191 [JLOI2013]卡牌游戏 概率dp

题面 题目传送门 解法 设\(f_{i,j}\)表示总共\(i\)个人,第\(j\)个人最终获胜的概率 枚举当前选择的是哪一张卡,那么就知道下一轮被淘汰的是谁了,假设是\(x\) 显然,下一轮的庄家就是\(x\)的下一个人 如果\(x=j\),那么可以不用管这种情况 如果\(x>j\),那么\(j\)在下一轮的编号为\(i-x+j\),否则为\(j-x\) 对应的两种情况转移一下即可 时间复杂度:\(O(n^2m)\) 代码 #include <bits/stdc++.h> #defin