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 }

时间: 2024-10-09 15:20:31

codeforces:Michael and Charging Stations分析和实现的相关文章

Codeforces Round #433 (Div. 1) D. Michael and Charging Stations(dp)

题目链接:Codeforces Round #433 (Div. 1) D. Michael and Charging Stations 题意: 一个人每天要加油,1种为1000,1种为2000,如果付全额,会得到10%的回扣放在卡上. 如果卡上有剩余的回扣,可以拿来抵现金.问n天最少需要花多少钱. 题解: 很直观的一个dp就是考虑dp[i][j],表示第i天卡上剩余回扣为j的最小花费. 将所有的数除以100后,j其实是小于40的,严格的说是小于30,官方题解有个证明. 因为卡上不可能积累很多的

cf 853 D Michael and Charging Stations [dp]

题面: 传送门 思路: 看到题目,第一思路是贪心,但是我很快就否决掉了(其实分类贪心也可以做) 然后就想,贪心不能解决的状态缺失,是否可以用dp来解决呢? 事实证明是可以的 我们设dp[i][j]表示第i天,还剩j*100积分的时候,最小花费的现金 有转移:dp[i][j]=min(dp[i-1][k]+cost[i]-(k-j)*100)(k=j+1...min(30,j+cost[i]/100) 最后再dp[i][j]=min(dp[i][j],dp[i-1][j-cost[i]/1000]

Codeforces 1175F 尺取法 性质分析

题意:给你一个数组,问有多少个区间,满足区间中的数构成一个排列. 思路(大佬代码):我们发现,一个排列一定含有1,所以我们不妨从1开始入手计算构成排列的区间个数.对于每个扫描到的1(假设处于位置i),我们向左右分别延伸,直到遇到1或者到了数组边界,延伸的时候,需要处理下左边到i的最大值和右边到i的最大值.处理之后,我们先处理排列的最大值在左端的情况.我们暴力枚举每一个位置,把它作为区间的左端点(L),现在我们去确认右端点(R),首先,右端点必须大于等于i,因为要包含1.我们先让右端点向左移,因为

Critical Bug Patched in Schneider Electric Vehicle Charging Station

Vulnerability in electric car charging stations could allow attackers to compromise devices. Schneider Electric is warning about a critical vulnerability in its EVLink Parking devices – a line of electric vehicle charging stations. The energy managem

UVALive 6485 Electric Car Rally (BFS,优先队列)

https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4496 In an attempt to demonstrate the practicality of electric cars, ElecCarCo is sponsoring a cross-country road rally. There are n chargin

UVALive 6485 Electric Car Rally (BFS,PQ)

https://icpcarchive.ecs.baylor.edu/index.php? option=com_onlinejudge&Itemid=8&page=show_problem&problem=4496 In an attempt to demonstrate the practicality of electric cars, ElecCarCo is sponsoring a cross-country road rally. There are n chargi

2018 数学建模美国赛思路

2018 数学建模 美国赛 D 建模思路 和文章大纲 题目 2018 ICM Problem D: Out of Gas and Driving on E (for electric, not empty) For both environmental and economic reasons, there is global interest in reducing the use of fossil fuels, including gasoline for cars. Whether mo

Alaska

题目描述 Problem B: Alaska The Alaska Highway runs 1422 miles from Dawson Creek, British Columbia to Delta Junction, Alaska. Brenda would like to be the first person to drive her new electric car the length of the highway. Her car can travel up to 200 mi

如何选择正确的HTTP状态码

本文来源于我在InfoQ中文站翻译的文章,原文地址是:http://www.infoq.com/cn/news/2015/12/how-to-choose-http-status-code 众所周知,每一个HTTP响应都会带有一个状态码,不过对于很多开发者来说,平时使用最多的几个状态码无外乎就是200.400.404.500等.那其他众多状态码该应用在何种场景中,什么时候应该使用哪些状态码就成为一个值得我们深入思考的问题了.即便在Facebook这样的公司中,那些聪明的开发者所构建的API也可能