【概率dp+FFT】CF553E Kyoya and Train

版权声明:这是蒟蒻的BLOG,神犇转载也要吱一声哦~ https://blog.csdn.net/Dream_Lolita/article/details/81812152

【题目】
原题地址
题目大意:一个有向图,走每条边有个代价,且花费的时间 [ 1 , T ] 有个概率 p e , i ,从1到 n ,若到达时时间超过 T ,则需要额外 X 的花费,问期望最小花费。

【题目分析】
myy论文题。

【解题思路】
来膜拜一发myy论文题。
首先考虑暴力dp, f x , t 表示到达 x 点,花费时间 t 期望的最小代价。那么对于 x 出发的每条边 e i = ( x i , y i , c i ) ,设 P e i , t 为走这条边概率,那么我们可以得到:

f x , t = M i n { c i + j = 1 T f y i , t + j × P e i , j }

用一个柿子 g e , t 表示 t 时刻从 x e 出发到达 n 点的最小期望费用,那么上面的柿子就可以写成:
g e , t = c e + j = 1 T f y e , t + j × P e , j

f i , t = M i n { g e , t } ( x e = i )

由于一个点不可能回到祖先,所以这幅图是一个DAG,我们可以直接dp,状态数是 O ( T m ) ,转移 O ( T ) ,果断跪掉。
当然如果我们将 P 数组翻转,实际上可以得到一个卷积的形式,可以用 F F T 优化。
g e , t = g e , T + t = c e + j = 1 T f y e , t + j × P e , T j

这样当然也过不了。因为右边的柿子和时间有关,我们可以考虑对时间分治。

这里给出一个结论:

D r = i = 0 n 1 f i g r i = i = 0 k f i g r i + i = k + 1 n 1 f i g r i

即计算卷积的时候,可以将贡献拆成几个部分来计算。

实际上推到这里我就已经晕了- -,不过还是要坚持(看别人blog)

分治过程比较奇怪,是先递归右边的。
1.对于当前的分治层[ l , r ] ,我们先递归处理右边 [ m i d + 1 , r ] ,得到右边所有的 f [ i ] [ m i d + 1 , r ] ,然后用FFT求值去贡献到 g [ e ] [ l , m i d ]
2.继续递归处理左边 [ l , m i d ]
3.当到达递归最底层即 l = r 时,我们用 g 去求出所有的 f [ i ] [ l ] ,因为此时的转移来源 g [ e ] [ t ] ( t > l ) 都计算完毕。

由于对于 t > T 的情况,已经超时所以我们不用管了,那么 F F T 的数组实际上只用开 2 T 就行了。
这个下标好奇怪啊- -

【参考代码】

#include<bits/stdc++.h>
using namespace std;

typedef double db;
const db pi=acos(-1);
const int INF=1e9;
const int N=2e5+10,M=110,K=4e4+10;
int n,m,L,S,T,X,rev[N];
db p[M][N],d[M][M],f[M][K],g[M][K];

int read()
{
    int ret=0,f=1;char c=getchar();
    while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
    while(isdigit(c)) {ret=ret*10+(c^48);c=getchar();}
    return f?ret:-ret;
}

struct Tway{int u,v,w;}e[N];

struct cd
{
    db r,i;
    cd(){}
    cd(db rr,db ii){r=rr;i=ii;}
    cd operator + (const cd&A)const {return cd(r+A.r,i+A.i);}
    cd operator - (const cd&A)const {return cd(r-A.r,i-A.i);}
    cd operator * (const cd&A)const {return cd(r*A.r-i*A.i,r*A.i+i*A.r);}
    cd operator / (const db&A)const {return cd(r/A,i/A);}
}a[N],b[N];

void fft(cd *a,int n,int f)
{
    for(int i=0;i<n;++i) if(i>rev[i]) swap(a[i],a[rev[i]]);
    for(int i=1;i<n;i<<=1)
    {
        cd wn(cos(pi/i),f*sin(pi/i));
        for(int j=0;j<n;j+=(i<<1))
        {
            cd w(1,0);
            for(int k=0;k<i;++k,w=w*wn)
            {
                cd x=a[j+k],y=w*a[i+j+k];
                a[j+k]=x+y;a[i+j+k]=x-y;
            }
        }
    }
    if(f==-1) for(int i=0;i<n;++i) a[i]=a[i]/n;
}

void init(int l,int r)
{
    int mid=(l+r)>>1;
    for(int j=1;j<=m;++j)
    {
        for(L=0,S=1;S<=(r<<1)-l-mid-1;S<<=1,++L);
        for(int i=0;i<S;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1));
        for(int i=0;i<S;++i) a[i]=b[i]=cd(0,0);
        for(int i=mid+1;i<=r;++i) a[i-mid-1].r+=f[e[j].v][i];
        for(int i=1;i<=r-l;++i) b[r-l-i].r+=p[j][i];
        fft(a,S,1);fft(b,S,1);
        for(int i=0;i<S;++i) a[i]=a[i]*b[i];
        fft(a,S,-1);
        for(int i=l;i<=mid;++i) g[j][i]+=a[i-mid-1+r-l].r;
    }
}

void solve(int l,int r)
{
    if(l==r)
    {
        for(int i=1;i<=m;++i) f[e[i].u][l]=min(f[e[i].u][l],g[i][l]+e[i].w);
        return;
    }
    int mid=(l+r)>>1;
    solve(mid+1,r);init(l,r);solve(l,mid);
}

int main()
{
#ifndef ONLINE_JUDGE
    freopen("CF553E.in","r",stdin);
    freopen("CF553E.out","w",stdout);
#endif
    n=read();m=read();T=read();X=read();
    for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(i^j) d[i][j]=INF; 
    for(int i=1;i<=m;++i)
    {
        e[i].u=read();e[i].v=read();e[i].w=read();
        d[e[i].u][e[i].v]=min(d[e[i].u][e[i].v],1.0*e[i].w);
        for(int j=1;j<=T;++j) p[i][j]=(db)read()/1e5; 
    }
    for(int k=1;k<=n;++k)
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
    for(int i=0;i<=T<<1;++i) f[n][i]=i<=T?0:X;
    for(int i=1;i<n;++i)
        for(int j=0;j<=T<<1;++j)
            f[i][j]=j<=T?INF:d[i][n]+X;
    init(1,T<<1);solve(0,T);
    printf("%.10lf\n",f[1][0]);

    return 0;
}

【总结】
myy!myy!

猜你喜欢

转载自blog.csdn.net/Dream_Lolita/article/details/81812152