codeforces:Michael and Charging Stations分析和实现

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了codeforces:Michael and Charging Stations分析和实现相关的知识,希望对你有一定的参考价值。

题目大意

  迈克尔接下来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分析和实现的主要内容,如果未能解决你的问题,请参考以下文章

Ch2 Relational model and Relational operation

ISL - Ch.6 Linear Model Selection and Regularization

Operating Systems Principles and Practice 2nd 2Ch Exercises

Operating Systems Principles and Practice 2nd 3Ch Exercises

《neural network and deep learning》题解——ch02 反向传播

《neural network and deep learning》题解——ch03 再看手写识别问题题解与源码分析