• The Preliminary Contest for ICPC Asia Nanjing 2019/2019南京网络赛——题解


    (施工中……已更新DF)

    比赛传送门:https://www.jisuanke.com/contest/3004

    D. Robots(期望dp)

    题意

    给一个DAG,保证入度为$0$的点只有$1$,出度为$0$的点只有$n$。

    现在一个机器人从$1$出发,每天都会以相同的概率前往相邻节点之一或静止不动。

    每天机器人消耗的耐久等于经过的天数。

    求机器人到点$n$期望消耗的耐久。

    划水划的很愉快,唯一一道做出来的题。但是和题解做法不同(感觉我的方法麻烦),因此砸了3h在这题上面(正在试图读懂题解ing)。

    设$f[u][j]$表示第$j$天从点$u$出发到$n$期望消耗的耐久,$out[i]$表示$i$的出度$+1$,那么答案就是$f[1][1]$。

    初始的方程就不写了很容易。

    经过一大顿推导可以求出$f[u][j]=frac{out[u]}{out[u]-1} imes j+frac{out[u]}{(out[u]-1)^2}+sum_v(frac{f[v][j+1]}{out[u]}+frac{f[v][j+2]}{out[u]^2}+...)$,其中$v$为$u$相邻节点。

    后面那点奇葩的东西很难处理,不妨我们先思考对于$1->2$这样的一个图,$f[1][j]$是多少?

    咦为什么这个东西是个等差数列?

    于是我们假设$f[v][j]$也是一个等差数列,则原式子可以化为$f[u][j]=frac{out[u]}{out[u]-1} imes j+frac{out[u]}{(out[u]-1)^2}+sum_v frac{f[v][j+1]*out[u]-f[v][j]}{(out[u]-1)^2}$,总之你能求出$f[u][j]$也是个等差数列就是了。

    于是数学归纳法可以求出所有的$f[u][j]$都是等差数列,因此我们$j$只需要求$1$和$2$,然后从后往前求即可,复杂度$O(n+m)$,细节和具体实现看代码。

    #include<cmath>
    #include<stack>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    typedef long double dl;
    const int N=1e5+5;
    const int M=2e5+5;
    inline int read(){
        int X=0,w=0;char ch=0;
        while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
        while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
        return w?-X:X;
    }
    struct node{
        int to,nxt;
    }e[M];
    int n,m,cnt,head[N],out[N],dep[N];
    dl f[N][3];
    inline void add(int u,int v){
        e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;out[u]++;
    }
    void init(){
        for(int i=1;i<=n;i++){
            head[i]=0;out[i]=1;
            f[i][1]=f[i][2]=0;
        }
        cnt=0;
    }
    dl F(int u,int j){
        if(u==n)return 0;
        if(f[u][1]>0&&f[u][2]>0)return (f[u][2]-f[u][1])*(j-1)+f[u][1];
        dl sum=(dl)out[u]/(out[u]-1)*j+(dl)out[u]/(out[u]-1)/(out[u]-1);
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            dl div=(out[u]-1)*(out[u]-1);
            dl a1=-F(v,j);dl a2=F(v,j+1)*out[u];
            sum+=(a1+a2)/div;
        }
        return f[u][j]=sum;
    }
    int main(){
        int T=read();
        for(int cas=1;cas<=T;cas++){
            n=read(),m=read();
            init();
            for(int i=1;i<=m;i++){
                int u=read(),v=read();add(u,v);
            }
            printf("%.2Lf
    ",F(1,1));
        }
        return 0;
    }
    View Code

    F. Greedy Sequence(线段树)

    题意

    给定一个长度为$n$的排列$a$,对每一个 $i in [1,n]$,定义一个序列$s_i$,规则如下:

    ①$s_i[1]=i$;

    ②对于每一个$jin [2,n],s_i[j]le s_i[j−1]$;

    ③对于每一个$jin [2,n],s_i[j],s_i[j-1]$ 在 $a$ 中的位置之差的绝对值$le k$,并且$a$中的每一个元素至多在$s_i$中出现一次;

    ④填不了时,用 $0$ 填充剩余的数至$s_i$长度为$n$为止;

    ⑤$s_i[j]$要尽可能的大;

    输出$|s_1|,|s_2|,...,|s_n|$,其中$|s_i|$为序列$s_i$中不为$0$的数的个数。

    显然我们要取的数是可取区间内最大的,并且容易发现,前一个数取$i$则后一个数一定会取某个数不变。

    于是维护$nxt[i]$表示$i$后面取的数,$nxt$数组可用线段树求出。

    #include<cmath>
    #include<stack>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int N=1e5+5;
    inline int read(){
        int X=0,w=0;char ch=0;
        while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
        while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
        return w?-X:X;
    }
    int n,k,w[N],id[N],nxt[N],sum[N];
    int tr[N<<2];
    void build(int a,int l,int r){
        if(l==r){
            tr[a]=w[l];return;
        }
        int mid=(l+r)>>1;
        build(a<<1,l,mid);build(a<<1|1,mid+1,r);
        tr[a]=max(tr[a<<1],tr[a<<1|1]);
    }
    void modify(int a,int l,int r,int x,int y){
        if(l==r){
            tr[a]=y;return;
        }
        int mid=(l+r)>>1;
        if(x<=mid)modify(a<<1,l,mid,x,y);
        else modify(a<<1|1,mid+1,r,x,y);
        tr[a]=max(tr[a<<1],tr[a<<1|1]);
    }
    int query(int a,int l,int r,int l1,int r1){
        if(r<l1||r1<l)return 0;
        if(l1<=l&&r<=r1)return tr[a];
        int mid=(l+r)>>1;
        return max(query(a<<1,l,mid,l1,r1),query(a<<1|1,mid+1,r,l1,r1));
    }
    inline void check(int x){
        int where=id[x];
        modify(1,1,n,where,0);
        nxt[x]=query(1,1,n,max(1,where-k),min(n,where+k));
    }
    int main(){
        int T=read();
        for(int cas=1;cas<=T;cas++){
            n=read(),k=read();
            for(int i=1;i<=n;i++){
                w[i]=read();id[w[i]]=i;
                sum[i]=0;
            }
            build(1,1,n);
            for(int i=n;i>=1;i--)check(i);
            for(int i=1;i<=n;i++)sum[i]+=sum[nxt[i]]+1;
            for(int i=1;i<=n;i++){
                if(i!=1)putchar(' ');
                printf("%d",sum[i]);
            }
            putchar('
    ');
        }
        return 0;
    }
    View Code

    +++++++++++++++++++++++++++++++++++++++++++

     +本文作者:luyouqi233。               +

     +欢迎访问我的博客:http://www.cnblogs.com/luyouqi233/+

    +++++++++++++++++++++++++++++++++++++++++++

  • 相关阅读:
    SSH免密码登陆备忘
    WeiBo官网oauth2开发文档理解
    TOP
    使用定位,逆地理编码,经纬度《=转=》地址信息、逆地理编码,地址《=转=》经纬度,贼方便!!!!
    计算机病毒分类之感染目标
    预处理
    指针与引用
    printf问题参数顺序
    神奇的求平均数
    C和C++的关系
  • 原文地址:https://www.cnblogs.com/luyouqi233/p/11444023.html
Copyright © 2020-2023  润新知