[BZOJ 4899] 记忆的轮廓

一、题目

点此看题

二、解法

首先考虑一下 50 50 50分的做法吧(每个点都可以建存档点),就是设 d p [ i ] dp[i] dp[i] i i i走到 n n n的期望步数,转移如下( d e g deg deg为度数):
d p [ i ] = 1 d e g ( d p [ i + 1 ] + ∑ v E [ v ] + 1 + d p [ i ] ) dp[i]=\frac{1}{deg}(dp[i+1]+\sum_{v}E[v]+1+dp[i]) dp[i]=deg1(dp[i+1]+vE[v]+1+dp[i])其中 E [ i ] E[i] E[i] i i i点的子树期望深度(也就是回到存档点的期望步数),我们移项,就可以得到:
d p [ i ] = d p [ i + 1 ] + ∑ v E [ u ] + 1 dp[i]=dp[i+1]+\sum_{v}E[u]+1 dp[i]=dp[i+1]+vE[u]+1再考虑 70 70 70分的做法,很容易想到 f [ i ] [ j ] f[i][j] f[i][j] i i i n n n,已经建了 j j j个存档点的期望步数,转移如下:
f [ i ] [ j ] = ∑ f [ k ] [ j − 1 ] + a [ i ] [ k ] f[i][j]=\sum f[k][j-1]+a[i][k] f[i][j]=f[k][j1]+a[i][k]其中 a [ i ] [ j ] a[i][j] a[i][j]是指 i i i j j j,存档点是 i i i的期望步数,可以用 50 50 50分做法类似的推出:
a [ i ] [ j ] = a [ i ] [ j − 1 ] × d e g [ j − 1 ] + d e g [ j − 1 ] + E [ j − 1 ] a[i][j]=a[i][j-1]\times deg[j-1]+deg[j-1]+E[j-1] a[i][j]=a[i][j1]×deg[j1]+deg[j1]+E[j1]这里的 E [ j − 1 ] E[j-1] E[j1]是指的所有和 j j j相连的不正确点的 E E E值和,这样做的时间复杂度是 O ( n 2 p ) O(n^2p) O(n2p)的。

考虑满分做法,我们观察数据范围,发现 50 ≤ p ≤ n 50\leq p\leq n 50pn,这说明了 n / p n/p n/p的最大值是 14 14 14,我们考虑一种特殊情况,就是把存档点均匀的放在 [ 1 , n ] [1,n] [1,n]中,可以估算出此时的答案上界大致是 1 e 12 1e12 1e12,而 a a a的增长是十分恐怖的,所以我们可以把步数设为 40 40 40(这其实是 log ⁡ a n s \log ans logans),就是说我们只考虑 40 40 40以内的转移点,那么时间复杂度降为 O ( n p log ⁡ a n s ) O(np\log ans) O(nplogans)

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 1505;
int read()
{
    int x=0,flag=1;
    char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int T,n,m,p,tot,f[M],deg[M];
double E[M],dp[M][M],a[M][M];
struct edge
{
    int v,next;
    edge(int V=0,int N=0) : v(V), next(N) {}
} e[2*M];
void dfs(int u,int fa)
{
    E[u]=0;
    if(!deg[u]) E[u]=1;
    for(int i=f[u]; i; i=e[i].next)
    {
        int v=e[i].v;
        if(v==fa) continue;
        dfs(v,u);
        if(fa) E[u]+=1.0/deg[u]*(E[v]+1);
        if(!fa) E[u]+=E[v];
    }
}
signed main()
{
    T=read();
    while(T--)
    {
        tot=0;
        memset(f,0,sizeof f);
        memset(deg,0,sizeof deg);
        n=read();
        m=read();
        p=read();
        for(int i=1; i<=m-n; i++)
        {
            int u=read(),v=read();
            e[++tot]=edge(v,f[u]),f[u]=tot;
            e[++tot]=edge(u,f[v]),f[v]=tot;
            deg[u]++;
        }
        for(int i=1; i<=n; i++)
            dfs(i,0);
        for(int i=1; i<=n; i++)
            for(int j=0; j<=p; j++)
                dp[i][j]=1e13;
        for(int i=1; i<n; i++)
            deg[i]++;
        for(int i=1; i<n; i++)
        {
            a[i][i]=0;
            for(int j=i+1; j<=min(n,i+40); j++)
                a[i][j]=a[i][j-1]*deg[j-1]+deg[j-1]+E[j-1];
        }
        for(int i=1; i<=p; i++)
            dp[n][i]=0;
        for(int i=n-1; i>=1; i--)
            for(int j=1; j<=p; j++)
                for(int k=i+1; k<=n; k++)
                {
                    if(k-i>40) break;
                    dp[i][j]=min(dp[i][j],dp[k][j-1]+a[i][k]);
                }
        printf("%.4lf\n",dp[1][p]);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值