CF97C Winning Strategy 构造、图论

题目传送门:http://codeforces.com/problemset/problem/97/C

题意:给出$n$与一个范围在$[0,1]$内的递增序列$P_0-P_n$,试构造一个无穷序列$\{a_i\}$满足$0 \leq a_i \leq n$,使得对于任意$k > 0$满足$a_k \leq \sum\limits_{i=1}^{k-1}(n - 2a_i)$且极限$\lim\limits_{m \rightarrow +\infty} \frac{\sum\limits_{i=1}^mp_{a_i}}{m}$达到最大,给出这个最大值。$N \leq 100$


 

因为数列无限,所以显然不能够将这个数列表示出来,故考虑构造一个循环节,使这个循环节的平均值最大,然后无限循环这个循环节,就能够得到最大的平均值。所以我们考虑找到一个有限数列使得其平均值最大。

方法一:

考虑转换模型。令$Q=\sum\limits_{i=1}^{k-1}(n - 2a_i)$,对于$a_k$,它会造成$P_{a_k}$的贡献并使得$Q+=n-2a_k=-a_k+(n-a_k)$,也就是待选的范围$Q$减掉了$a_k$,又补上了$n-a_k$。故考虑:最开始我们有一个箱子,其中有$0$个物品,第$i$次从箱子中拿出$a_i$个物品,并补进$n-a_i$个物品,求$\lim\limits_{m \rightarrow +\infty} \frac{\sum\limits_{i=1}^mp_{a_i}}{m}$的最大值。

接下来我们能发现某一个时刻的决策只与其对应的$Q$有关,与时刻无关,所以我们可以将$Q$的取值看成点,决策转换看成边,边权是对应的贡献,构出一个图。可以知道其中对应箱子中物品数量大于$2n$的点显然是没有意义的,因为从其中取出$n$个得到$P_n$的贡献仍然能够覆盖所有的$0-n$的取值。所以我们的点数只有$2n$个($0$点也是没有意义的,因为最优解一定不会出现在$0$号点)。

回到我们需要求的东西,是一个循环节,对应在图中是一个环。所以我们需要找的是图上一个平均边权最大的环。这个显然是可以二分的,check函数将所有边权减掉mid,用spfa判断正环即可。时间复杂度约为$O(N^3)$

 1 #include<bits/stdc++.h>
 2 #define R register
 3 #define MAXN 100010
 4 #define eps 1e-9
 5 using namespace std;
 6 
 7 double p[111] , dis[210];
 8 short N , K;
 9 queue < int > q;
10 struct Edge{
11     int end , upEd;
12     double w;
13 }Ed[MAXN];
14 int head[210] , flo[210] , cntEd;
15 bool inq[210];
16 
17 inline void addEd(int a , int b , double c){
18     Ed[++cntEd].end = b;
19     Ed[cntEd].w = c;
20     Ed[cntEd].upEd = head[a];
21     head[a] = cntEd;
22 }
23 
24 inline bool check(double mid){
25     while(!q.empty())
26         q.pop();
27     memset(dis , 0xdd , sizeof(dis));
28     memset(inq , 0 , sizeof(inq));
29     memset(flo , 0 , sizeof(flo));
30     dis[flo[1] = 1] = 0;
31     q.push(1);
32     while(!q.empty()){
33         int t = q.front();
34         q.pop();
35         inq[t] = 0;
36         for(int i = head[t] ; i ; i = Ed[i].upEd)
37             if(dis[Ed[i].end] < dis[t] + Ed[i].w - mid){
38                 dis[Ed[i].end] = dis[t] + Ed[i].w - mid;
39                 flo[Ed[i].end] = flo[t] + 1;
40                 if(flo[Ed[i].end] > K)
41                     return 1;
42                 if(!inq[Ed[i].end]){
43                     inq[Ed[i].end] = 1;
44                     q.push(Ed[i].end);
45                 }
46             }
47     }
48     return 0;
49 }
50 
51 inline void solve(){
52     for(int i = 0 ; i <= K ; i++)
53         for(int j = 0 ; j <= K ; j++){
54             int k = i - j;
55                 if(k <= N && k >= -N && (k & 1) == (N & 1))
56                     addEd(i , j , p[N + i - j >> 1]);
57         }
58     double l = 0 , r = 1;
59     while(r - l > eps){
60         double mid = (l + r) / 2;
61         check(mid) ? l = mid : r = mid;
62     }
63     printf("%.8lf" , l);
64 }
65 
66 int main(){
67     scanf("%d" , &N);
68     K = N << 1;
69     for(R short i = 0 ; i <= N ; ++i)
70         scanf("%lf" , &p[i]);
71     solve();
72     return 0;
73 }

方法二:

由我们的图论模型可以得出一个结论:我们只会选一种$n-2a_i>0$的$a_i$和一种$n-2a_i<0$的$a_i$。

证明:

考虑同时选取了$a_j$与$a_k$使得$n-2a_j<0$且$n-2a_k<0$,考虑进行$(n-2a_j)(n-2a_k)$的物品拿出

对于选择$j$有$P_j(n-2a_k)$的贡献,对于选择$k$有$P_k(n-2a_j)$的贡献,我们假设$P_j(n-2a_k)>P_k(n-2a_j)$

那么我们进行无限次之后,假定这个次数为$(n-2a_j)(n-2a_k)$的倍数,这样选择$j$的贡献仍然大于选择$k$的贡献,故选择$j$更好。

大于$0$的情况考虑物品贡献即可。

所以我们可以枚举较小的一个$a_i$和较大的一个$a_j$算出以它们为循环节时的答案,所有的答案取$max$即可,时间复杂度为$O(n^2)$

注意:当$n$为偶数时,$\frac{n}{2}$需要特殊判断。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 
 4 long double p[101];
 5 
 6 int main(){
 7     int N;
 8     long double ans = 0;
 9     cin >> N;
10     for(int i = 0 ; i <= N ; i++)
11         cin >> p[i];
12     for(int i = 0 ; i <= N - 1 >> 1 ; i++)
13         for(int j = (N >> 1) + 1 ; j <= N ; j++)
14             if((p[i] * (2 * j - N) + p[j] * (N - 2 * i)) / (- 2 * i + 2 * j) > ans)
15                 ans = (p[i] * (2 * j - N) + p[j] * (N - 2 * i)) / (- 2 * i + 2 * j);
16     if((N & 1) == 0)
17         ans = max(ans , p[N >> 1]);
18     cout << fixed << setprecision(9) << ans;
19     return 0;
20 }

 

转载于:https://www.cnblogs.com/Itst/p/9762380.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值