记忆的轮廓

出题大佬题解:https://blog.csdn.net/WerKeyTom_FTD/article/details/53026266

期望DP题,感觉很有意思(第一次接触到50%,70%类似题解,步步优化的感觉稍爽)。

%出题人...

50%算法:数据中点明有50%数据n==p,此时不用考虑存档点设在哪里(所有的正确节点),那就与p无瓜,思路较简单。

设f[i]表示i到n的期望步数(f[n]==0)。关键:考虑错误节点的贡献:

在走到正确节点时等概率选择儿子,选择正确儿子贡献很显然:(f[i+1]+1)*1/son[i]

那对于正确节点的错误儿子就得知到它期望走到读档(回到正确节点)的步数,错误节点只有错误儿子所以比较简单

:g[i]=1/son[i]*sigma(g[j]+f[i]+1),j为其儿子。

所以f[i]=(f[i+1]+1)*1/son[i]+1/son[i]*sigma(g[j]+f[i]+1);较关键的是这,因为期望步数等于这种情况发生的概率乘上该情况步数,

走到i的错节点儿子回去的期望步数为f[i]+1+g[j],乘出来移项,两个括号里的1可以合并成一个1从括号里干出来。

预处理出i节点所有错误儿子的g[]值和记为s[i],(可以直接算s[],不用算g[],省空间)。

关键点二:1/son[i]*sigma(g[i]+f[i])乘出来等于的是son[i]-1/son[i] *f[i].

移项得f[i]=son[i]+f[i+1]+s[i];

线性的...

70%算法:

这时要考虑存档点的设定了。所以设f[i][j]表示当前存档点是i还剩j次存档机会的最优解。(刚走到i更新存档点为i)

设a[i][j]表示当前存档点是i走到j的期望步数-->辅助f[i][j]转移。

a[i][j]=(a[i][j-1]+1)*1/son[j-1]+1/son[j-1]*sigma(1+g[k]+a[i][j]),k是j的错儿子,同样可以提出1合并成1个1。

问题来了为什么是+a[i][j]呢,当初真的想了两节课:(可以手动模拟一下这个过程)

走到j-1的错儿子后,再走进行读档回到的是i而不是j-1(不要和50%算法混了)。所以i到j得在走一遍,所以加上了a[i][j]而不是a[i][j-1].

移项->a[i][j]=a[i][j-1]*son[j-1]+son[j-1]+s[j-1].

预处理出a[i][j]以方便f[i][j]转移。

f[i][j]=f[k][j-1]+a[i][k],k是下一次的存档位置。

所以转移可以把i写在最外面倒序,也可以把j写在外维i则正序即可。

复杂度O(n^2p);

100%算法:..目前只学来一种还是懂了思想自己不会推的那种

摘抄出题大佬解析:

{

不过,有没有发现,70%和100%好像都没考虑一个问题。观察a数组,可以看到它是恐怖的增长的,我们最终答案会不会爆炸?
我们来估计答案的上界。考虑一种可行方案,每n/p个正确节点就设立一次存档位置,那么答案最大是多少呢?考虑最坏情况,观察a的转移,应该每变换一次存档点,大约需要3^(n/p)s[i]+3(n/p-1)*s[i+1]+3^(n/p-2)*s[i+2]+……
因为最多m个节点,s的上限是1500(实际上也远远达不到),把所有s都视为这个上限,提取公因数,计算一下那个等比数列求和,由于p是有下界的,因此n/p有上界14,发现最后也就是个12位数的样子,那么我们估计出答案最大也不会超过这个,可以放心做了。而至于a会爆炸的问题,double是可以存很多位的,而且太大的a肯定不可能被用上。

那么其实,针对答案不会特别大,a的增长又很恐怖,我们还可以思考对70%的算法优化。那就是设定一个常数step,每次转移最多从距当前step步远的位置转移过来。step取40多基本不会有问题了,因为a的下界已经是2^40了,而答案的上界远远没有达到,经过精确计算还可以再把step调小一点。
复杂度O(np log ans)

}

这种分析法还是第一次见,分析答案的上界从而卡去无用的计算。

当然感觉单队正解还是很正常的。

 

sd错误:清空了链向星结构体没清空head数组...

总结收获:1.根据题目数据分析部分得分算法

                  2.dp里一般把题目的主信息用上看是否能够转移(节点,存档点,剩余存档次数)。再加上一些预处理。

                  3.只颓到了这种思想,开阔眼界吧...:根据分析答案的上界和算法中数的上下界卡去无用计算,让算法飞起来。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=2500;
int n,m,p;
double f[maxn][maxn],a[maxn][maxn],s[maxn];
int head[maxn],cnt=1,son[maxn];
struct node{
   int to,next;
}line[maxn*2];
void add(int x,int y)
{
    line[cnt].to=y;
    line[cnt].next=head[x];
    head[x]=cnt++;
    son[x]++;
}
void dfs(int x)
{
   for(int i=head[x];i;i=line[i].next)
   {
    int v=line[i].to;
        dfs(v);
        s[x]+=1.00/son[x]*s[v];
   }
   s[x]+=1;
}
int read()
{
   int t=0;
   char ch;
   ch=getchar();
   while(ch<'0'||ch>'9') ch=getchar();
   while(ch>='0'&&ch<='9')  t=t*10+ch-48,ch=getchar();
   return t;
}
int main()
{
   int t,x,y;
   t=read();
   while(t--)
   {
      cnt=1;
      memset(f,0x7f,sizeof(f));
      memset(son,0,sizeof(son));
      memset(s,0,sizeof(s));
      memset(head,0,sizeof(head));
      memset(line,0,sizeof(line));
      n=read(); m=read(); p=read();
      for(int i=n;i<m;i++)
      {
       x=read();  y=read();
          add(x,y);
      }
      for(int i=1;i<n;i++)
      {
      son[i]++;
      for(int j=head[i];j;j=line[j].next)
      {
          dfs(line[j].to);
          s[i]+=s[line[j].to];
      }
      }
      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]*son[j-1]+son[j-1]+s[j-1];
     }
      }
      for(int i=1;i<=p;i++) f[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;
              f[i][j]=min(f[k][j-1]+a[i][k],f[i][j]);
         }
     }
      }
      printf("%.4lf\n",f[1][p]);
   }
}
View Code

 

posted @ 2019-06-25 17:02  three_D  阅读(361)  评论(0编辑  收藏  举报