[关闭]
@IOU 2018-10-14T07:33:23.000000Z 字数 3595 阅读 939

CF97C Winning Strategy

倍增floyd 负环 分数规划


今天心情不大好,因为各种原因今天爆0...QAQ
首要原因就是这道杠了两个多小时的T1.
最开始没有给样例解释,手玩了好久的样例发现怎么也凑不出,后来才知道是无穷的,凑得出才怪了.其实给了样例解释之后就暗示这题可以二分逼近答案.
此题有三种方法:

倍增floyd

看到题这个算法就在脑子中间闪过,然而,,,仅仅是闪过而已.
先处理出转移矩阵,表示走步,剩余i个1滴血随从转移到j个1滴血的随从的最小值.
但如果你每次只砍一排,然后接着再换一批新的再砍一排,以此无限地砍下去,那么状态是无限的.是无限的吗?
考虑缩减状态数.显然当我们的人数在2n以内,都是必须要保留的.超过2n我们可以先砍掉.由于是无穷,所以我们的1滴血的人数可以是负数,因为可以忽略前面几项,前面几次操作可以都是增加1滴血的人数的.
于是直接倍增就好了

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<iostream>
  4. #include<cmath>
  5. #include<algorithm>
  6. #include<queue>
  7. #define maxn 205
  8. #define ll long long
  9. using namespace std;
  10. int n,cur,pre,m,cnt;
  11. double inf,p[maxn],ans,tim;
  12. struct matrix
  13. {
  14. double a[maxn][maxn];
  15. void init(){memset(a,0xc2,sizeof(a));}
  16. double* operator [] (int x){return a[x];}
  17. matrix operator * (matrix x)
  18. {
  19. matrix y;y.init();
  20. for(int i=1;i<=m;i++)
  21. for(int j=1;j<=m;j++)
  22. for(int k=1;k<=m;k++)
  23. if(a[i][k]!=inf&&x[k][j]!=inf)
  24. y[i][j]=max(y[i][j],a[i][k]+x[k][j]);
  25. return y;
  26. }
  27. }mat[35],f;
  28. int main()
  29. {
  30. //freopen("blasphemy.in","r",stdin);
  31. //freopen("blasphemy.out","w",stdout);
  32. cin>>n;m=n<<1;
  33. for(int i=0;i<=n;i++)scanf("%lf",&p[i]);
  34. for(int i=0;i<=30;i++)mat[i].init();f.init();
  35. inf=f[0][0];f[0][0]=0;
  36. for(int i=0;i<=m;i++)//转移矩阵
  37. for(int j=0;j<=n;j++)
  38. {
  39. int k=i+n-j-j;
  40. if(i-j>=0&&k>=0&&k<=m)mat[0][i][k]=max(mat[0][i][k],p[j]);
  41. }
  42. cnt=1;pre=1;
  43. for(int c=1;cnt<=30;c<<=1,cnt++)
  44. {
  45. for(int i=0;i<=m;i++)
  46. if(f[cur][i]!=inf)
  47. {
  48. for(int j=0;j<=m;j++)
  49. if(mat[cnt-1][i][j]!=inf)
  50. f[pre][j]=max(f[pre][j],f[cur][i]+mat[cnt-1][i][j]);
  51. f[cur][i]=inf;
  52. }
  53. tim+=c;mat[cnt]=mat[cnt-1]*mat[cnt-1];swap(cur,pre);
  54. for(int i=0;i<=m;i++)ans=max(ans,f[cur][i]/tim);
  55. }
  56. printf("%.10lf\n",ans);
  57. return 0;
  58. }

分数规划

最后我们是要得到最大的ans使得下面的式子恒成立,ans越小越有可能合法.


像上面处理转移矩阵一样建图,二分ans,跑出负环说明合法(因为可以在负环上面无限绕),check更大的ans

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<iostream>
  4. #include<cmath>
  5. #include<algorithm>
  6. #include<queue>
  7. #define maxn 305
  8. #define maxm 500005
  9. #define ll long long
  10. using namespace std;
  11. int n,vis[maxn],o[maxn],cn[maxn],nxt[maxm],head[maxn];
  12. int to[maxm],cnt,m;
  13. double p[maxn],mid,w[maxm],dis[maxn];
  14. const double eps=1e-10;
  15. queue<int> q;
  16. void add(int u,int v,double ww)
  17. {
  18. nxt[++cnt]=head[u];head[u]=cnt;
  19. to[cnt]=v;w[cnt]=ww;
  20. }
  21. bool spfa(int s)
  22. {
  23. dis[s]=0;while(!q.empty())q.pop();
  24. q.push(s);vis[s]=1;
  25. while(!q.empty())
  26. {
  27. int u=q.front();q.pop();++cn[u];
  28. vis[u]=1;o[u]=0;if(cn[u]==m)return 1;
  29. for(int i=head[u];i;i=nxt[i])
  30. {
  31. int v=to[i];
  32. if(dis[v]>dis[u]+w[i]+mid)
  33. {
  34. dis[v]=dis[u]+w[i]+mid;
  35. if(!o[v])q.push(v),o[v]=1;
  36. }
  37. }
  38. }
  39. return 0;
  40. }
  41. bool check()
  42. {
  43. memset(o,0,sizeof(o));
  44. memset(vis,0,sizeof(vis));
  45. memset(cn,0,sizeof(cn));
  46. for(int i=0;i<=m;i++)dis[i]=1e15;
  47. for(int i=0;i<=m;i++)
  48. if(!vis[i])if(spfa(i))return 1;
  49. return 0;
  50. }
  51. int main()
  52. {
  53. //freopen("blasphemy.in","r",stdin);
  54. //freopen("blasphemy.out","w",stdout);
  55. cin>>n;m=n*2;
  56. for(int i=0;i<=n;i++)scanf("%lf",&p[i]);
  57. for(int i=0;i<=m;i++)
  58. for(int j=0;j<=n;j++)
  59. {
  60. int k=i+n-j-j;
  61. if(k>0&&k<=m)add(i,k,-p[j]);
  62. }
  63. double l=0,r=1;
  64. while(r-l>eps)
  65. {
  66. mid=(l+r)*0.5;
  67. if(check())l=mid;
  68. else r=mid;
  69. }
  70. printf("%.10lf\n",l);
  71. return 0;
  72. }

结论

每一种p对应一种修改:表示增加了场上个1血人数,于是我们要是他们一直循环下去得到一个最优解,其实就是求,每个背包有个重量,价值,要求重量和为0.
这个结论其实考场上也yy了,只是觉得肯定是错的于是去想怎么合并多个符号相同的价值.其实完全是不要考虑的,因为一定是一个正数和一个负数是最优的,(不知道证明,感性理解).于是我们只要枚举一个正的,一个负的,直接算他们的的最大值就可以了.

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<iostream>
  4. #include<cmath>
  5. #include<algorithm>
  6. #include<queue>
  7. #define maxn 205
  8. #define ll long long
  9. using namespace std;
  10. int n;
  11. double c[maxn],p[maxn],ans;
  12. int main()
  13. {
  14. //freopen("blasphemy.in","r",stdin);
  15. //freopen("blasphemy.out","w",stdout);
  16. cin>>n;
  17. for(int i=0;i<=n;i++)scanf("%lf",&p[i]);
  18. for(int i=0;i<=n;i++)c[i]=fabs(n-2*i);
  19. for(int i=0;i<=(n-1)/2;i++)
  20. for(int j=(n-1)/2+1;j<=n;j++)
  21. ans=max(ans,(c[j]*p[i]+c[i]*p[j])/(c[j]+c[i]));
  22. printf("%.12lf",ans);
  23. return 0;
  24. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注