bzoj4030【HEOI2015】小L的白日梦
题意:http://www.lydsy.com/JudgeOnline/problem.php?id=4030
sol :orz Yousiki http://www.cnblogs.com/yousiki/p/6490769.html
orz jiry http://jiruyi910387714.is-programmer.com/posts/90425.html
有三个结论:
1、一定存在一个最优解,使得每天不高兴的概率单调递增
这个结论很显然,考虑对于一组逆序对将其交换,答案更优
2、一定存在最优解,由排序后的一段前缀+一段后缀构成
如果把前缀一个点放到中间比原来优,那么把这个点放到后面一定更优(或者一样)
3、每一种选取的项目有三种可能:选一个,全选,其他,且第三种至多一个
这个不会证QAQ,Yousiki大佬说随便想想就知道了QAQ
这样的话就可以假设第三种情况在后缀(前后缀分别做一次),
线性往后扫前缀,每次向后跳一个块,考虑后缀的期望会怎么更新,再处理一个快+1的情况即可
可以把一个点拆成三个:1,cnt,1,这样写起了方便一些,不用再特判
代码莫名其妙数据一大就WA.......完全不知道为什么QAQ,弃疗了,cogs最后一个点还莫名其妙过了....50points
#include<iostream> #include<algorithm> #include<cstdio> #include<cmath> #define inf 1e18 #define double long double #define int long long using namespace std; const int Mx=100010; struct Node { int c; double p,w; } A[3*Mx],tmp[Mx]; bool cmp(Node a,Node b) { return a.p>b.p; } int n,k,tot,now,sigma; double ans,sum; void solve() { sum=0,now=1,sigma=k; for(int i=n;i>=1;i--) sum+=(A[i].c-1)*A[i].w+(1-A[i].p)*A[i+1].p,sigma-=A[i].c; for(int i=1;i<=n;i++) { sigma-=A[i].c; while(now<=n&&sigma<=0) sum-=(A[now].c-1)*A[now].w+(1-A[now].p)*A[now+1].p,sigma+=A[now++].c; if(sigma<=0) break; sum+=(A[i].c-1)*A[i].w+(1-A[i-1].p)*A[i].p; ans=min(ans,sum+(sigma-1)*A[now-1].w+(1-A[now-1].p)*A[now].p+(1-A[i].p)*A[now-1].p); } sigma=k,sum=0;//初始状态 for(int i=1,mn=min(sigma,A[i].c);i<=n&&mn;i++) sigma-=mn,sum+=(mn-1)*A[i].w+(1-A[i-1].p)*A[i].p; ans=min(ans,sum); } signed main() { int T; scanf("%lld",&T); while(T--) { scanf("%lld%lld",&n,&k); for(int i=1,x,y;i<=n;i++) scanf("%lld/%lld%lld",&x,&y,&tmp[i].c),tmp[i].p=(double) x/y,tmp[i].w=(1-tmp[i].p)*tmp[i].p; sort(tmp+1,tmp+1+n,cmp); tot=0; for(int i=1;i<=n;i++)//拆点 { if(tmp[i].c==1) A[++tot]=tmp[i]; if(tmp[i].c>1) A[++tot]=tmp[i],A[tot].c=1,A[++tot]=tmp[i],A[tot].c--; if(tmp[i].c>2) A[tot].c--,A[++tot]=tmp[i],A[tot].c=1; } n=tot; ans=inf,A[0].p=1,A[n+1].p=0; solve(); for(int i=1;i<=(n+1)/2;i++) A[i].p=1-A[i].p,A[n-i+1].p=1-A[n-i+1].p,swap(A[i],A[n-i+1]); solve(); printf("%.6LF\n",fabs(ans)); } }