【BZOJ2832&&3874】宅男小C [模拟退火][贪心]

宅男小C

Time Limit: 10 Sec  Memory Limit: 256 MB
[Submit][Status][Discuss]

Description

  众所周知,小C是个宅男,所以他的每天的食物要靠外卖来解决。小C现在有M元钱,他想知道这些钱他最多可以吃多少天。

  餐厅提供N种食物,每种食物有两个属性,单价Pi和保质期Si,表示小C需要花Pi元才能买到足够一天吃的这种食物,并且需要在送到Si天内吃完,否则食物会变质,就不能吃了,若Si为0则意味着必须在送到当天吃完。另外,每次送餐需要额外F元送餐费。

Input

  每个测试点包含多组测试数据;
  每个测试数据第一行三个整数M,F,N,如题目描述中所述;
  以下N行,每行两个整数,分别表示PiSi

Output

  对于每个测试数据输出一行,表示最多可以吃的天数。

Sample Input

  32 5 2
  5 0
  10 2
  10 10 1
  10 10
  10 1 1
  1 5

Sample Output

  3
  0
  8

HINT

  对于40%的数据,M,Si <= 2*10^6;
  对于100%的数据,1 ≤ N ≤ 200,M, Si<= 10^18,1 ≤ T ≤ 50,1 ≤ F ≤ M,1 ≤ Pi ≤ M。

Main idea

  每种食物有一个花费和一个保质期,在保质期内食用可以多活一天,每次购买可以买多个食物,买一次会耗费一些钱,问最多能活几天。

Solution

  我们先从简单的做法入手,如果确定了购买次数,能求出最多活几天吗?答案是显然可以的。我们运用贪心:首先,若存在某种价格又贵保质期又短的食物显然是没有用的,我们sort一遍直接删去,然后我们可以得到一个价格上升且保质期上升的序列。我们基于这里开始贪心:我们先从便宜的食物入手,显然每次都是从这种食物吃起,仅存在两种不购买便宜的情况:1.保质期过了;2.钱不够满足所有次数了。如果保质期过了,我们就选择下一个食物,如果钱不够满足所有次数了,那就能买几次买几次,记录一下答案,退出。

  我们解决了确定购买次数最多活几天之后,再仔细思考:由于购买会花钱,那么我们大胆猜测购买次数和活的天数有一定的规律,我们画了几张图之后,发现其比例大致单峰,如下图所示:

  我们发现,显然中间有一段波动,那么就不能使用三分法了。那怎么办呢?但是我们再发现:函数最后波动段非常短!显然在随机范围内可行,那么显然我们可以使用随机化算法!这里我们运用模拟退火。直接模拟退火随机一个购买次数,然后Judge更新即可。

  随机化算法是坠吼的!\(≧▽≦)/

Code

  1 #include<iostream>  
  2 #include<algorithm>  
  3 #include<cstdio>  
  4 #include<cstring>  
  5 #include<cstdlib>  
  6 #include<cmath>  
  7 using namespace std;
  8 typedef long long s64;
  9    
 10 const int ONE = 10005;
 11 const int INF = 2147483640;
 12   
 13 int n;
 14 s64 A,Now,Ans;
 15 s64 Total,F;
 16   
 17 struct power
 18 {
 19         s64 cost;
 20         s64 days;
 21 }a[ONE];
 22   
 23 bool cmp(const power &a,const power &b)
 24 {
 25         if(a.cost == b.cost) return a.days > b.days;
 26         return a.cost < b.cost;
 27 }
 28   
 29 int get()
 30 {
 31         int res=1,Q=1;    char c;
 32         while( (c=getchar())<48 || c>57)
 33         if(c=='-')Q=-1;
 34         if(Q) res=c-48; 
 35         while((c=getchar())>=48 && c<=57) 
 36         res=res*10+c-48; 
 37         return res*Q; 
 38 }
 39   
 40 void pre()
 41 {
 42         sort(a+1,a+n+1,cmp);    s64 d=-1;
 43         int m=n;    n=0;
 44         for(int i=1;i<=m;i++)
 45         if(a[i].days > d)
 46             a[++n]=a[i], d=a[i].days;
 47 }
 48   
 49 s64 Judge(s64 times)
 50 {
 51         if(times<=0) return 0;
 52         s64 Money = Total - times * F;
 53         s64 res = 0, num, day = 0;
 54         for(int i=1;i<=n;i++)
 55         {
 56             num = min(Money / a[i].cost / times, a[i].days - day + 1);
 57             Money -= num * a[i].cost * times;
 58             day += num; res += times * num;
 59             if(day <= a[i].days)
 60             {
 61                 num = Money / a[i].cost;
 62                 res += num;
 63                 Ans = max(Ans, res);
 64                 return res;
 65             }
 66         }
 67         Ans = max(Ans, res);
 68         return res;
 69 }
 70   
 71 double Random() {return rand()/(double)RAND_MAX;}
 72 void SA(double T)
 73 {
 74         Now = 1;
 75         while(T >= 1)
 76         {
 77             A = Now + (s64)(T * (Random()*2-1)) ;
 78             if(A<=0) A = T*Random();
 79             s64 dE = Judge(A) - Judge(Now);
 80             if(dE > 0)
 81                 Now = A;
 82             T *= 0.93;
 83         }
 84          
 85 }
 86   
 87 void Solve()
 88 {
 89         for(int i=1;i<=n;i++)
 90             scanf("%lld %lld",&a[i].cost,&a[i].days);
 91         pre();
 92         Ans = 0;
 93         SA(Total / F + 1);
 94         printf("%lld\n",Ans);
 95 }
 96  
 97 int main()
 98 {
 99         while(scanf("%lld %lld %d",&Total,&F,&n) != EOF)
100             Solve();
101 }
View Code

 

posted @ 2017-03-13 14:47  BearChild  阅读(764)  评论(0编辑  收藏  举报