2018.02.12 noip模拟赛T2
二兵的赌注
Description
游戏中,二兵要进入了一家奇怪的赌场。
赌场中有n个庄家,每个庄家都可以猜大猜小,猜一次一元钱。
每一次开彩前,你都可以到任意个庄家那里下赌注。
如果开彩结果是大,你就可以得到你之前猜大的庄家相应的ai元钱。
如果开彩结果是小,你就可以得到你之前猜小的庄家相应的bi元钱。
你可以在同一个庄家那里既猜大又猜小,(这样是两块钱),也可以什么都不猜(这样不用钱)。
问怎么样下注,才能赢得最多的有保障的钱。
有保障的钱指不管开彩结果是大是小,你都能够赢得相应的钱。
你能帮助他计算这个值吗?
Input
第一行一个数字N。表示有N个庄家。
接下来N行,每行2个实数,分别表示这个庄家的ai和bi。
Output
一个四位小数,表示最多能赢的有保障的钱。
Sample Input
4
1.4 3.7
1.2 2
1.6 1.4
1.9 1.5
Sample Output
0.5000
HINT
样例中,最好的策略是赌第一个庄家开小,第三、第四个庄家开大
测试点 N
1 N≤10
2
3 N≤1000
4
5
6
7 N≤100000
8
9
10
1.0 ≤ ai, bi ≤ 1000.0
1s/128M
好了,拿到题先花一定的时间弄清题意。
然后我们就会发现:押大小和哪个庄家之间完全没有关系,一个庄家的大和小完全是独立的。继续分析,显然押越大的越好。
于是我们分别sort一下。
然后我开始了分析:首先读入时-=1,然后,设大或小为a[]和b[],分别取前i.j个押。
然后对于每对确定的i,j,易知s=min(suma[i]-j,sumb[j]-i)其中sum为前缀和。
我们设suma[i]-j=左,sumb[j]-i=右
然后定性分析:
当j固定时,i+则左+右-,i-则左-右+
当i固定时,j+则左-右+,j-则左+右-
再次分析可知对于一对(i,j),若i+,则可能存在的ans>s必须令j+
于是有了以下思路:for(i:1...n) 对于每个i找到匹配的j,则i++时j不用从头开始枚举,只需继续j++,于是时间复杂度O(n)完成(不记sort)
以上是我在考场上的思路。不管思考过程多么艰难以至于花费1hour才想出来,但是结果就是,和标准程序简直一模一样。但是代码实现上就很naive了。
同一个思路,我代码实现起来写了都要40+行,再一看标程,不到十行就解决了,尴尬......
我的代码大概是这样
1 #include <cstdio> 2 using namespace std; 3 int a[10],b; 4 void c(); 5 int main() 6 { 7 8 ///读入 9 ///sort一波 10 /** 11 for(i:1...n) 12 { 13 ax=xx 14 bx=xx 15 c=xx 16 while(j<=n && j<=c+a[i]) 17 { 18 if(xXX) 19 xxx 20 else if(xxx) 21 xxx 22 else 23 xxx 24 } 25 d=xx 26 e=xx 27 f=xx 28 } 29 */ 30 return 0; 31 }
瞎设变量,大概近十个。
然后结果就是:推了一小时,打了半小时,最后又调了半小时。结果还是错的跟翔一样。
还有一个很奇怪的事:scanf读入double会出现蜜汁读入0,int就好得很。只好用cin缓慢读入了。找时间问一下雨菲,她肯定又不会理我。。。
那么来看看标程:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <assert.h> 5 #include <iostream> 6 #include <sstream> 7 #include <vector> 8 #include <string> 9 #include <math.h> 10 #include <queue> 11 #include <list> 12 #include <algorithm> 13 #include <map> 14 #include <set> 15 #include <stack> 16 #include <ctime> 17 using namespace std; 18 19 #define ALL(c) (c).begin(),(c).end() 20 #define IN(x,c) (find(c.begin(),c.end(),x) != (c).end()) 21 #define REP(i,n) for (int i=0;i<(int)(n);i++) 22 #define FOR(i,a,b) for (int i=(a);i<=(b);i++) 23 #define INIT(a,v) memset(a,v,sizeof(a)) 24 #define SORT_UNIQUE(c) (sort(c.begin(),c.end()), c.resize(distance(c.begin(),unique(c.begin(),c.end())))) 25 template<class A, class B> A cvt(B x) { stringstream ss; ss<<x; A y; ss>>y; return y; } 26 27 typedef pair<int,int> PII; 28 typedef long long int64; 29 30 int n=0; 31 double a[100000],b[100000]; 32 33 int main() { 34 freopen("coin.in","r",stdin); 35 freopen("coin.out","w",stdout); 36 cin >> n; 37 REP (i,n) { 38 cin >> a[i] >> b[i]; 39 a[i]-=1; b[i]-=1; 40 } 41 sort(a,a+n); reverse(a,a+n); 42 sort(b,b+n); reverse(b,b+n); 43 int i=0,j=0; 44 double r=0; 45 double sa=0,sb=0; 46 while (i<n) { 47 sa+=a[i]; 48 i++; 49 while (j<n && min(sa-j,sb-i)<min(sa-(j+1),(sb+b[j])-i)) { 50 sb+=b[j]; 51 j++; 52 } 53 r=max(r, min(sa-j,sb-i)); 54 } 55 printf("%.4f\n",r); 56 return 0; 57 }
那么我们忽略上面一长串的头文件和宏定义。仔细观察关键部分可以发现:我那冗杂的程序和变量得到了极大的简化,强!喵!喵不可言啊!
接下来是文字答案中的二分,利用了单调性。
给出来:
Coin
算法零:
输出0即可。
算法一:
直接O(4^n)暴力枚举,期望得分20分。
算法二:
贪心,问题所求的是最大化min(∑ai ?na ?nb,∑bj ?na ?nb),na, nb分别表示猜大猜小的个数。将ai, bi全部减一,所求变为min(∑ai ?nb,∑bj ?na)。我们枚举na, nb,贪心取最大的na个ai和nb个ai即可。
复杂度O(n^2),期望得分60分
算法三:
贪心+二分,假设na固定,那么ai的选取也是确定的。假设nb从0开始一个个往上增加,∑bj –na递增,∑ai –nb递减。min(∑ai ?nb,∑bj ?na) 在∑bj –na超过∑ai –nb前递增。所以只需二分∑bj –na什么时候超过∑ai –nb即可。
复杂度O(n log n),期望得分100分。
标程:
直接sort+O(n)
思路跟我的一样可惜我TM写了30-40行的东西他十行不到搞定。
仍需努力呀。
我自己打了测了一下,最大的一个数据正确,但是跑了1.2s还是1.4s(算上了编译时间)
我的代码:
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 using namespace std; 5 const int N=100004; 6 int n; 7 double ans=-1; 8 double a[N],b[N]; 9 double suma[N],sumb[N]; 10 bool cmp(double a,double b){return a>=b;} 11 int tw_divide(int l,int r,int i) 12 { 13 if(l==r) return r; 14 int mid=(l+r)>>1; 15 double now=min(suma[i]-mid,sumb[mid]-i); 16 double nowl=min(suma[i]-mid+1,sumb[mid-1]-i); 17 double nowr=min(suma[i]-mid-1,sumb[mid+1]-i); 18 if(now>=nowl && now>=nowr) return mid; 19 if(now<nowl) return tw_divide(l,mid-1,i); 20 if(now<nowr) return tw_divide(mid+1,r,i); 21 } 22 int main() 23 { 24 freopen("coin.in","r",stdin); 25 //freopen("coin.out","w",stdout); 26 scanf ("%d",&n); 27 for(int i=1;i<=n;i++) 28 { 29 cin>>a[i]>>b[i]; 30 a[i]-=1;b[i]-=1; 31 } 32 sort(a+1,a+n+1,cmp); 33 sort(b+1,b+n+1,cmp); 34 35 for(int i=1;i<=n;i++) 36 { 37 suma[i]=suma[i-1]+a[i]; 38 sumb[i]=sumb[i-1]+b[i]; 39 } 40 double now; 41 int j; 42 for(int i=1;i<=n;i++) 43 { 44 j=tw_divide(1,n,i); 45 now=min(suma[i]-j,sumb[j]-i); 46 ans=max(ans,now); 47 } 48 printf("%.4f",ans); 49 return 0; 50 }
我还是比较喜欢第一种。二分时间多个logn而且还难得写。