码迷,mamicode.com
首页 > 其他好文 > 详细

codeforces:Michael and Charging Stations分析和实现

时间:2017-09-22 10:06:56      阅读:175      评论:0      收藏:0      [点我收藏+]

标签:model   catch   向量   [1]   ble   alt   时间复杂度   admin   超过   

题目大意

  迈克尔接下来n天里分别需要支付C[1], C[2], ... , C[n]费用,但是每次支付费用可以选择使用优惠或不使用优惠,每次使用价值X的优惠那么迈克尔所能使用的优惠余量将减少X并且当天所需要支付的费用将减少X,而第一天迈克尔所持有的优惠余量为0。如果不使用优惠,那么优惠余量将增加X/10,其中X是当天迈克尔所支付的费用。

  输入规模为1<=n<=3e5,而C[1], ... , C[n]只可能取1000或2000。


思路

  下面说明一下我个人的思路:

  一个解决方案可以归结为每日所使用的优惠量,即解决方案可以视作一个n维向量,而一个解决方案是有效的,当且仅当到每一天余留的优惠量可以支付当天的优惠使用量。一个有效解决方案S是最优的,当且仅当该解决方案各个维度的加总最大,即SUM(S)=S[1]+...+S[n]最大。

  首先可以很简单地证明如下定理:

  定理1:对于任意有效的解决方案S,若1<=i<j<=n且C[i]=C[j]且S[i]>0且S[j]<C[i],则可以以特定额度减少第i天使用的优惠量并等额增加第j天使用的优惠量,得到新的解决方案S‘,满足S‘是有效的解决方案且SUM(S‘)>=SUM(S)。

  证明:考虑两种情况:若S[j]=0,则可以将额度设置为S[i],否则额度可以设置为1。由于等量转移,因此被第i天所使用的优惠量延迟到第j天使用,显然这样不会违背有效性的定义。而当S[j]=0时,由于额度为S[i],因此到S[j]日结束剩余的优惠量不会发生改变(第i天提供了原本第j天额外提供的优惠量)。

  由于最优的解决方案可能会有很多,没有目标的寻找容易丢失方向,接下来就确定要找哪一个特定的解决方案。下面给最优的两个不同解决方案S1,S2引入与字符串比较相同的偏序关系:若S1<S2,当且仅当存在1<=j<=n使得S1[j]<S2[j]且对于任意1<=i<j,满足S1[i]=S2[i]。而我们要找的就是最大的最优解决方案,称之为目标解决方案。

  目标解决方案的性质有很多,下面逐一推导。

  由定理1可以了解到目标解决方案B必定满足条件:若第i天使用了优惠,那么所有后续的日子j,由C[i]=C[j]能推出B[j]=C[j]。

  • 记H2为所有当日需要支付费用为2000的日子中最早使用了优惠的日子,记H1为所有当日需要支付费用为1000的日子中最早使用了优惠的日子。若H2<H1,则B[H1]=1000,若H2>H1,则B[H2]=2000。
  • 记H12是H1之后首个费用为1000的日子,若H12<H2,则H12和H2之间不存在费用为2000的日子。
  • 若H2<H1,则必定在H2和H1之间不存在两个费用为1000的日子。在前面前提下若C[H2]<=1000,则在H2和H1之间不存在一个费用为1000的日子。

  有了上面这些目标解决方案的必要条件,只需要遍历所有满足这些条件的H1和H2的组合,并挑选其中SUM值最大的解决方案,可以保证最终得到的必定是最优解决方案(未必是目标解决方案)。下面是算法的具体流程:

  分别尝试H1<H2和H2<H1两种情况,进行线性迭代,寻找SUM值最大的解决方案。

  在H1<H2的前提下,对于任意可能的H1,H2必定处于H1,H12之间或H12后首个费用为2000的日子。由于H1最多有n种可能取值,而在H1,H12之间费用为2000的日子与H1的组合数目不会超过n(每一个费用为2000的日子只可能与前一个费用为1000的日子组合),H12后首个费用为2000的日子与H1的组合数也不会超过n,故总共可能的组合数不会超过2n。

  在H1>H2的前提下,对于任意可能的H2,则H1可能是H2后前两个费用为2000的日子。由于H1最多有n种可能取值,而每个H1对应两个可能的H2,故总共可能的组合数不会超过2n。

  因此总的时间复杂度是O(n)。

  上面没有解决在O(1)时间复杂度内判断某个特定的H1, H2组合是否有效。首先开辟一个长度为n的数组R,R[i]记录截至到第i天之前最多能增加的优惠量(即第1,...,i-1日均不使用优惠,所累计的优惠量)。同时开辟一个长度为n的数组A,且A[i]=C[i]+C[i+1]+...+C[n]。依据上面流程优惠使用情况可以综合为两种情况:第一种是从第y天起每天都使用足量优惠抵消当日所有费用,且只有一天x<y使用了优惠。第二种是从第x天起(除了某天y>x)每天都使用优惠,且从x+1天起每一个使用优惠的日子使用的优惠抵消当日费用。要计算两种情况下x天能使用的最大优惠量,可以按照下面的公式计算出来:

  第一种情况:$$ allowed=\min\left(C\left[x\right],min\left(R\left[x\right],\,\,R\left[y\right]-A\left[y\right]-\frac{C\left[x\right]}{10}\right)\right) $$

  第二种情况:$$ allowed=\min\left(\min\left(R\left[x\right],R\left[x\right]+\frac{C\left[y\right]}{10}-A\left[y+1\right]\right)-\left(A\left[x+1\right]-A\left[y\right]\right),C\left[x\right]\right) $$

  其中allowed是允许在第x使用的最大优惠,解决方案无效当且仅当allowed为负数。两种情况在预先计算出R和A的情况下可以以O(1)的时间复杂度计算出来。


代码 

  下面给出JAVA代码,140ms通过:

技术分享
  1 import java.io.BufferedInputStream;
  2 import java.io.IOException;
  3 import java.io.InputStream;
  4 import java.io.PushbackInputStream;
  5 import java.math.BigDecimal;
  6 
  7 /**
  8  * Created by Administrator on 2017/9/21.
  9  */
 10 public class MichaelAndChargingStations {
 11     int totalDay; //The total day number
 12     int[] costs; //The cost for each day
 13     int[] sumUp; //sumUp[i] = cost[i] + cost[i + 1] + ... + cost[totalDay - 1]
 14     int[] remain; //remain[i] = remian[0] + remain[1] + ... + remain[i - 1]
 15 
 16     public static void main(String[] args) {
 17         MichaelAndChargingStations solution = new MichaelAndChargingStations();
 18         solution.init();
 19         int result = solution.solve();
 20         System.out.println(result);
 21     }
 22 
 23     public void init() {
 24         try {
 25             AcmInputReader input = new AcmInputReader(System.in);
 26 
 27             totalDay = input.nextInteger();
 28             costs = new int[totalDay];
 29             for (int i = 0; i < totalDay; i++) {
 30                 costs[i] = input.nextInteger();
 31             }
 32 
 33         } catch (IOException e) {
 34             throw new RuntimeException(e);
 35         }
 36     }
 37 
 38     public int solve() {
 39         remain = new int[totalDay];
 40         sumUp = new int[totalDay + 1];
 41 
 42         remain[0] = 0;
 43         for (int i = 0, bound = totalDay - 1; i < bound; i++) {
 44             remain[i + 1] = remain[i] + costs[i] / 10;
 45         }
 46         sumUp[totalDay] = 0;
 47         for (int i = totalDay - 1; i >= 0; i--) {
 48             sumUp[i] = sumUp[i + 1] + costs[i];
 49         }
 50 
 51         int maxConsume = 0;
 52 
 53 
 54         int h1, h2;
 55 
 56         //Try H1 < H2
 57         h1 = preIndex(1000, totalDay);
 58         h2 = totalDay;
 59         while (h1 >= 0) {
 60             int allowed = maxAllowedUse(h1, h2);
 61             if (allowed < 0) {
 62                 break;
 63             }
 64 
 65             maxConsume = Math.max(maxConsume, allowed + sumUp[h2]);
 66 
 67             h2--;
 68             if (h2 == h1) {
 69                 h1 = preIndex(1000, h1);
 70             }
 71         }
 72 
 73         //Try H2 < H1
 74         int h1before = preIndex(1000, totalDay);
 75         h2 = preIndex(2000, totalDay);
 76         h1 = totalDay;
 77         while (h2 >= 0) {
 78             if (h1before > h2) {
 79                 h1 = h1before;
 80                 h1before = preIndex(1000, h1before);
 81                 continue;
 82             }
 83 
 84             int allowed;
 85             allowed = maxAllowedUseWithInterval(h2, h1);
 86             if (allowed < 0) {
 87                 break;
 88             }
 89             maxConsume = Math.max(maxConsume, allowed + sumOf(h2 + 1, h1) + sumOf(h1 + 1, totalDay));
 90 
 91             allowed = maxAllowedUseWithInterval(h2, totalDay);
 92             if (allowed >= 0) {
 93                 maxConsume = Math.max(maxConsume, allowed + sumOf(h2 + 1, totalDay));
 94             }
 95 
 96             h2 = preIndex(2000, h2);
 97         }
 98 
 99         return sumUp[0] - maxConsume;
100     }
101 
102     /**
103      * This function calculate a model that from day blockstart, we use enough bonus to feed the cost.
104      * And the day index is the only day before blockStart that use bonus, so how many bonus day index can use?
105      */
106     int maxAllowedUse(int index, int blockStart) {
107         if (blockStart >= totalDay) {
108             return Math.min(remain[index], costs[index]);
109         }
110         return Math.min(Math.min(remain[blockStart] - (sumUp[blockStart] + costs[index] / 10), remain[index]), costs[index]);
111     }
112 
113     /**
114      * A simple function to sum up costs[from], cost[from + 1], ... , costs[to -1]
115      */
116     int sumOf(int from, int to) {
117         if (from >= to) {
118             return 0;
119         }
120         return sumUp[from] - sumUp[to];
121     }
122 
123     /**
124      * This function solve a problem, that all the day from index except day interval all use bouns, and all the day use bonus feed the cost other than day index.
125      * So how many bonus day index can use?
126      */
127     int maxAllowedUseWithInterval(int index, int interval) {
128         if (interval >= totalDay) {
129             return Math.min(remain[index] - sumUp[index + 1], costs[index]);
130         }
131         return Math.min(Math.min(remain[index], remain[index] + costs[interval] / 10 - sumUp[interval + 1]) - sumOf(index + 1, interval), costs[index]);
132     }
133 
134     int preIndex(int val, int cur) {
135         int i;
136         for (i = cur - 1; i >= 0 && costs[i] != val; i--) ;
137         return i;
138     }
139 
140     /**
141      * @author dalt
142      * @see java.lang.AutoCloseable
143      * @since java1.7
144      */
145     static class AcmInputReader implements AutoCloseable {
146         private PushbackInputStream in;
147 
148         /**
149          * 创建读取器
150          *
151          * @param input 输入流
152          */
153         public AcmInputReader(InputStream input) {
154             in = new PushbackInputStream(new BufferedInputStream(input));
155         }
156 
157         @Override
158         public void close() throws IOException {
159             in.close();
160         }
161 
162         private int nextByte() throws IOException {
163             return in.read() & 0xff;
164         }
165 
166         /**
167          * 如果下一个字节为b,则跳过该字节
168          *
169          * @param b 被跳过的字节值
170          * @throws IOException if 输入流读取错误
171          */
172         public void skipByte(int b) throws IOException {
173             int c;
174             if ((c = nextByte()) != b) {
175                 in.unread(c);
176             }
177         }
178 
179         /**
180          * 如果后续k个字节均为b,则跳过k个字节。这里{@literal k<times}
181          *
182          * @param b     被跳过的字节值
183          * @param times 跳过次数,-1表示无穷
184          * @throws IOException if 输入流读取错误
185          */
186         public void skipByte(int b, int times) throws IOException {
187             int c;
188             while ((c = nextByte()) == b && times > 0) {
189                 times--;
190             }
191             if (c != b) {
192                 in.unread(c);
193             }
194         }
195 
196         /**
197          * 类似于{@link #skipByte(int, int)}, 但是会跳过中间出现的空白字符。
198          *
199          * @param b     被跳过的字节值
200          * @param times 跳过次数,-1表示无穷
201          * @throws IOException if 输入流读取错误
202          */
203         public void skipBlankAndByte(int b, int times) throws IOException {
204             int c;
205             skipBlank();
206             while ((c = nextByte()) == b && times > 0) {
207                 times--;
208                 skipBlank();
209             }
210             if (c != b) {
211                 in.unread(c);
212             }
213         }
214 
215         /**
216          * 读取下一块不含空白字符的字符块
217          *
218          * @return 下一块不含空白字符的字符块
219          * @throws IOException if 输入流读取错误
220          */
221         public String nextBlock() throws IOException {
222             skipBlank();
223             StringBuilder sb = new StringBuilder();
224             int c = nextByte();
225             while (AsciiMarksLazyHolder.asciiMarks[c = nextByte()] != AsciiMarksLazyHolder.BLANK_MARK) {
226                 sb.append((char) c);
227             }
228             in.unread(c);
229             return sb.toString();
230         }
231 
232         /**
233          * 跳过输入流中后续空白字符
234          *
235          * @throws IOException if 输入流读取错误
236          */
237         private void skipBlank() throws IOException {
238             int c;
239             while ((c = nextByte()) <= 32) ;
240             in.unread(c);
241         }
242 
243         /**
244          * 读取下一个整数(可正可负),这里没有对溢出做判断
245          *
246          * @return 下一个整数值
247          * @throws IOException if 输入流读取错误
248          */
249         public int nextInteger() throws IOException {
250             skipBlank();
251             int value = 0;
252             boolean positive = true;
253             int c = nextByte();
254             if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) {
255                 positive = c == ‘+‘;
256             } else {
257                 value = ‘0‘ - c;
258             }
259             c = nextByte();
260             while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
261                 value = (value << 3) + (value << 1) + ‘0‘ - c;
262                 c = nextByte();
263             }
264 
265             in.unread(c);
266             return positive ? -value : value;
267         }
268 
269         /**
270          * 判断是否到了文件结尾
271          *
272          * @return true如果到了文件结尾,否则false
273          * @throws IOException if 输入流读取错误
274          */
275         public boolean isMeetEOF() throws IOException {
276             int c = nextByte();
277             if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.EOF) {
278                 return true;
279             }
280             in.unread(c);
281             return false;
282         }
283 
284         /**
285          * 判断是否在跳过空白字符后抵达文件结尾
286          *
287          * @return true如果到了文件结尾,否则false
288          * @throws IOException if 输入流读取错误
289          */
290         public boolean isMeetBlankAndEOF() throws IOException {
291             skipBlank();
292             int c = nextByte();
293             if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.EOF) {
294                 return true;
295             }
296             in.unread(c);
297             return false;
298         }
299 
300         /**
301          * 获取下一个用英文字母组成的单词
302          *
303          * @return 下一个用英文字母组成的单词
304          */
305         public String nextWord() throws IOException {
306             StringBuilder sb = new StringBuilder(16);
307             skipBlank();
308             int c;
309             while ((AsciiMarksLazyHolder.asciiMarks[(c = nextByte())] & AsciiMarksLazyHolder.LETTER_MARK) != 0) {
310                 sb.append((char) c);
311             }
312             in.unread(c);
313             return sb.toString();
314         }
315 
316         /**
317          * 读取下一个长整数(可正可负),这里没有对溢出做判断
318          *
319          * @return 下一个长整数值
320          * @throws IOException if 输入流读取错误
321          */
322         public long nextLong() throws IOException {
323             skipBlank();
324             long value = 0;
325             boolean positive = true;
326             int c = nextByte();
327             if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) {
328                 positive = c == ‘+‘;
329             } else {
330                 value = ‘0‘ - c;
331             }
332             c = nextByte();
333             while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
334                 value = (value << 3) + (value << 1) + ‘0‘ - c;
335                 c = nextByte();
336             }
337             in.unread(c);
338             return positive ? -value : value;
339         }
340 
341         /**
342          * 读取下一个浮点数(可正可负),浮点数是近似值
343          *
344          * @return 下一个浮点数值
345          * @throws IOException if 输入流读取错误
346          */
347         public float nextFloat() throws IOException {
348             return (float) nextDouble();
349         }
350 
351         /**
352          * 读取下一个浮点数(可正可负),浮点数是近似值
353          *
354          * @return 下一个浮点数值
355          * @throws IOException if 输入流读取错误
356          */
357         public double nextDouble() throws IOException {
358             skipBlank();
359             double value = 0;
360             boolean positive = true;
361             int c = nextByte();
362             if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) {
363                 positive = c == ‘+‘;
364             } else {
365                 value = c - ‘0‘;
366             }
367             c = nextByte();
368             while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
369                 value = value * 10.0 + c - ‘0‘;
370                 c = nextByte();
371             }
372 
373             if (c == ‘.‘) {
374                 double littlePart = 0;
375                 double base = 1;
376                 c = nextByte();
377                 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
378                     littlePart = littlePart * 10.0 + c - ‘0‘;
379                     base *= 10.0;
380                     c = nextByte();
381                 }
382                 value += littlePart / base;
383             }
384             in.unread(c);
385             return positive ? value : -value;
386         }
387 
388         /**
389          * 读取下一个高精度数值
390          *
391          * @return 下一个高精度数值
392          * @throws IOException if 输入流读取错误
393          */
394         public BigDecimal nextDecimal() throws IOException {
395             skipBlank();
396             StringBuilder sb = new StringBuilder();
397             sb.append((char) nextByte());
398             int c = nextByte();
399             while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
400                 sb.append((char) c);
401                 c = nextByte();
402             }
403             if (c == ‘.‘) {
404                 sb.append(‘.‘);
405                 c = nextByte();
406                 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
407                     sb.append((char) c);
408                     c = nextByte();
409                 }
410             }
411             in.unread(c);
412             return new BigDecimal(sb.toString());
413         }
414 
415         private static class AsciiMarksLazyHolder {
416             public static final byte BLANK_MARK = 1;
417             public static final byte SIGN_MARK = 1 << 1;
418             public static final byte NUMERAL_MARK = 1 << 2;
419             public static final byte UPPERCASE_LETTER_MARK = 1 << 3;
420             public static final byte LOWERCASE_LETTER_MARK = 1 << 4;
421             public static final byte LETTER_MARK = UPPERCASE_LETTER_MARK | LOWERCASE_LETTER_MARK;
422             public static final byte EOF = 1 << 5;
423             public static byte[] asciiMarks = new byte[256];
424 
425             static {
426                 for (int i = 0; i <= 32; i++) {
427                     asciiMarks[i] = BLANK_MARK;
428                 }
429                 asciiMarks[‘+‘] = SIGN_MARK;
430                 asciiMarks[‘-‘] = SIGN_MARK;
431                 for (int i = ‘0‘; i <= ‘9‘; i++) {
432                     asciiMarks[i] = NUMERAL_MARK;
433                 }
434                 for (int i = ‘a‘; i <= ‘z‘; i++) {
435                     asciiMarks[i] = LOWERCASE_LETTER_MARK;
436                 }
437                 for (int i = ‘A‘; i <= ‘Z‘; i++) {
438                     asciiMarks[i] = UPPERCASE_LETTER_MARK;
439                 }
440                 asciiMarks[0xff] = EOF;
441             }
442         }
443     }
444 }
View Code

 

codeforces:Michael and Charging Stations分析和实现

标签:model   catch   向量   [1]   ble   alt   时间复杂度   admin   超过   

原文地址:http://www.cnblogs.com/dalt/p/7528841.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!