题目大意

  迈克尔接下来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通过:

 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PushbackInputStream;
 import java.math.BigDecimal;

 /**
  * Created by Administrator on 2017/9/21.
  */
 public class MichaelAndChargingStations {
     int totalDay; //The total day number
     int[] costs; //The cost for each day
     int[] sumUp; //sumUp[i] = cost[i] + cost[i + 1] + ... + cost[totalDay - 1]
     int[] remain; //remain[i] = remian[0] + remain[1] + ... + remain[i - 1]

     public static void main(String[] args) {
         MichaelAndChargingStations solution = new MichaelAndChargingStations();
         solution.init();
         int result = solution.solve();
         System.out.println(result);
     }

     public void init() {
         try {
             AcmInputReader input = new AcmInputReader(System.in);

             totalDay = input.nextInteger();
             costs = new int[totalDay];
             for (int i = 0; i < totalDay; i++) {
                 costs[i] = input.nextInteger();
             }

         } catch (IOException e) {
             throw new RuntimeException(e);
         }
     }

     public int solve() {
         remain = new int[totalDay];
         sumUp = new int[totalDay + 1];

         remain[0] = 0;
         for (int i = 0, bound = totalDay - 1; i < bound; i++) {
             remain[i + 1] = remain[i] + costs[i] / 10;
         }
         sumUp[totalDay] = 0;
         for (int i = totalDay - 1; i >= 0; i--) {
             sumUp[i] = sumUp[i + 1] + costs[i];
         }

         int maxConsume = 0;

         int h1, h2;

         //Try H1 < H2
         h1 = preIndex(1000, totalDay);
         h2 = totalDay;
         while (h1 >= 0) {
             int allowed = maxAllowedUse(h1, h2);
             if (allowed < 0) {
                 break;
             }

             maxConsume = Math.max(maxConsume, allowed + sumUp[h2]);

             h2--;
             if (h2 == h1) {
                 h1 = preIndex(1000, h1);
             }
         }

         //Try H2 < H1
         int h1before = preIndex(1000, totalDay);
         h2 = preIndex(2000, totalDay);
         h1 = totalDay;
         while (h2 >= 0) {
             if (h1before > h2) {
                 h1 = h1before;
                 h1before = preIndex(1000, h1before);
                 continue;
             }

             int allowed;
             allowed = maxAllowedUseWithInterval(h2, h1);
             if (allowed < 0) {
                 break;
             }
             maxConsume = Math.max(maxConsume, allowed + sumOf(h2 + 1, h1) + sumOf(h1 + 1, totalDay));

             allowed = maxAllowedUseWithInterval(h2, totalDay);
             if (allowed >= 0) {
                 maxConsume = Math.max(maxConsume, allowed + sumOf(h2 + 1, totalDay));
             }

             h2 = preIndex(2000, h2);
         }

         return sumUp[0] - maxConsume;
     }

     /**
      * This function calculate a model that from day blockstart, we use enough bonus to feed the cost.
      * And the day index is the only day before blockStart that use bonus, so how many bonus day index can use?
      */
     int maxAllowedUse(int index, int blockStart) {
         if (blockStart >= totalDay) {
             return Math.min(remain[index], costs[index]);
         }
         return Math.min(Math.min(remain[blockStart] - (sumUp[blockStart] + costs[index] / 10), remain[index]), costs[index]);
     }

     /**
      * A simple function to sum up costs[from], cost[from + 1], ... , costs[to -1]
      */
     int sumOf(int from, int to) {
         if (from >= to) {
             return 0;
         }
         return sumUp[from] - sumUp[to];
     }

     /**
      * 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.
      * So how many bonus day index can use?
      */
     int maxAllowedUseWithInterval(int index, int interval) {
         if (interval >= totalDay) {
             return Math.min(remain[index] - sumUp[index + 1], costs[index]);
         }
         return Math.min(Math.min(remain[index], remain[index] + costs[interval] / 10 - sumUp[interval + 1]) - sumOf(index + 1, interval), costs[index]);
     }

     int preIndex(int val, int cur) {
         int i;
         for (i = cur - 1; i >= 0 && costs[i] != val; i--) ;
         return i;
     }

     /**
      * @author dalt
      * @see java.lang.AutoCloseable
      * @since java1.7
      */
     static class AcmInputReader implements AutoCloseable {
         private PushbackInputStream in;

         /**
          * 创建读取器
          *
          * @param input 输入流
          */
         public AcmInputReader(InputStream input) {
             in = new PushbackInputStream(new BufferedInputStream(input));
         }

         @Override
         public void close() throws IOException {
             in.close();
         }

         private int nextByte() throws IOException {
             return in.read() & 0xff;
         }

         /**
          * 如果下一个字节为b,则跳过该字节
          *
          * @param b 被跳过的字节值
          * @throws IOException if 输入流读取错误
          */
         public void skipByte(int b) throws IOException {
             int c;
             if ((c = nextByte()) != b) {
                 in.unread(c);
             }
         }

         /**
          * 如果后续k个字节均为b,则跳过k个字节。这里{@literal k<times}
          *
          * @param b     被跳过的字节值
          * @param times 跳过次数,-1表示无穷
          * @throws IOException if 输入流读取错误
          */
         public void skipByte(int b, int times) throws IOException {
             int c;
             while ((c = nextByte()) == b && times > 0) {
                 times--;
             }
             if (c != b) {
                 in.unread(c);
             }
         }

         /**
          * 类似于{@link #skipByte(int, int)}, 但是会跳过中间出现的空白字符。
          *
          * @param b     被跳过的字节值
          * @param times 跳过次数,-1表示无穷
          * @throws IOException if 输入流读取错误
          */
         public void skipBlankAndByte(int b, int times) throws IOException {
             int c;
             skipBlank();
             while ((c = nextByte()) == b && times > 0) {
                 times--;
                 skipBlank();
             }
             if (c != b) {
                 in.unread(c);
             }
         }

         /**
          * 读取下一块不含空白字符的字符块
          *
          * @return 下一块不含空白字符的字符块
          * @throws IOException if 输入流读取错误
          */
         public String nextBlock() throws IOException {
             skipBlank();
             StringBuilder sb = new StringBuilder();
             int c = nextByte();
             while (AsciiMarksLazyHolder.asciiMarks[c = nextByte()] != AsciiMarksLazyHolder.BLANK_MARK) {
                 sb.append((char) c);
             }
             in.unread(c);
             return sb.toString();
         }

         /**
          * 跳过输入流中后续空白字符
          *
          * @throws IOException if 输入流读取错误
          */
         private void skipBlank() throws IOException {
             int c;
             while ((c = nextByte()) <= 32) ;
             in.unread(c);
         }

         /**
          * 读取下一个整数(可正可负),这里没有对溢出做判断
          *
          * @return 下一个整数值
          * @throws IOException if 输入流读取错误
          */
         public int nextInteger() throws IOException {
             skipBlank();
             int value = 0;
             boolean positive = true;
             int c = nextByte();
             if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) {
                 positive = c == '+';
             } else {
                 value = '0' - c;
             }
             c = nextByte();
             while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
                 value = (value << 3) + (value << 1) + '0' - c;
                 c = nextByte();
             }

             in.unread(c);
             return positive ? -value : value;
         }

         /**
          * 判断是否到了文件结尾
          *
          * @return true如果到了文件结尾,否则false
          * @throws IOException if 输入流读取错误
          */
         public boolean isMeetEOF() throws IOException {
             int c = nextByte();
             if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.EOF) {
                 return true;
             }
             in.unread(c);
             return false;
         }

         /**
          * 判断是否在跳过空白字符后抵达文件结尾
          *
          * @return true如果到了文件结尾,否则false
          * @throws IOException if 输入流读取错误
          */
         public boolean isMeetBlankAndEOF() throws IOException {
             skipBlank();
             int c = nextByte();
             if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.EOF) {
                 return true;
             }
             in.unread(c);
             return false;
         }

         /**
          * 获取下一个用英文字母组成的单词
          *
          * @return 下一个用英文字母组成的单词
          */
         public String nextWord() throws IOException {
             StringBuilder sb = new StringBuilder(16);
             skipBlank();
             int c;
             while ((AsciiMarksLazyHolder.asciiMarks[(c = nextByte())] & AsciiMarksLazyHolder.LETTER_MARK) != 0) {
                 sb.append((char) c);
             }
             in.unread(c);
             return sb.toString();
         }

         /**
          * 读取下一个长整数(可正可负),这里没有对溢出做判断
          *
          * @return 下一个长整数值
          * @throws IOException if 输入流读取错误
          */
         public long nextLong() throws IOException {
             skipBlank();
             long value = 0;
             boolean positive = true;
             int c = nextByte();
             if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) {
                 positive = c == '+';
             } else {
                 value = '0' - c;
             }
             c = nextByte();
             while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
                 value = (value << 3) + (value << 1) + '0' - c;
                 c = nextByte();
             }
             in.unread(c);
             return positive ? -value : value;
         }

         /**
          * 读取下一个浮点数(可正可负),浮点数是近似值
          *
          * @return 下一个浮点数值
          * @throws IOException if 输入流读取错误
          */
         public float nextFloat() throws IOException {
             return (float) nextDouble();
         }

         /**
          * 读取下一个浮点数(可正可负),浮点数是近似值
          *
          * @return 下一个浮点数值
          * @throws IOException if 输入流读取错误
          */
         public double nextDouble() throws IOException {
             skipBlank();
             double value = 0;
             boolean positive = true;
             int c = nextByte();
             if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) {
                 positive = c == '+';
             } else {
                 value = c - '0';
             }
             c = nextByte();
             while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
                 value = value * 10.0 + c - '0';
                 c = nextByte();
             }

             if (c == '.') {
                 double littlePart = 0;
                 double base = 1;
                 c = nextByte();
                 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
                     littlePart = littlePart * 10.0 + c - '0';
                     base *= 10.0;
                     c = nextByte();
                 }
                 value += littlePart / base;
             }
             in.unread(c);
             return positive ? value : -value;
         }

         /**
          * 读取下一个高精度数值
          *
          * @return 下一个高精度数值
          * @throws IOException if 输入流读取错误
          */
         public BigDecimal nextDecimal() throws IOException {
             skipBlank();
             StringBuilder sb = new StringBuilder();
             sb.append((char) nextByte());
             int c = nextByte();
             while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
                 sb.append((char) c);
                 c = nextByte();
             }
             if (c == '.') {
                 sb.append('.');
                 c = nextByte();
                 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
                     sb.append((char) c);
                     c = nextByte();
                 }
             }
             in.unread(c);
             return new BigDecimal(sb.toString());
         }

         private static class AsciiMarksLazyHolder {
             public static final byte BLANK_MARK = 1;
             public static final byte SIGN_MARK = 1 << 1;
             public static final byte NUMERAL_MARK = 1 << 2;
             public static final byte UPPERCASE_LETTER_MARK = 1 << 3;
             public static final byte LOWERCASE_LETTER_MARK = 1 << 4;
             public static final byte LETTER_MARK = UPPERCASE_LETTER_MARK | LOWERCASE_LETTER_MARK;
             public static final byte EOF = 1 << 5;
             public static byte[] asciiMarks = new byte[256];

             static {
                 for (int i = 0; i <= 32; i++) {
                     asciiMarks[i] = BLANK_MARK;
                 }
                 asciiMarks['+'] = SIGN_MARK;
                 asciiMarks['-'] = SIGN_MARK;
                 for (int i = '0'; i <= '9'; i++) {
                     asciiMarks[i] = NUMERAL_MARK;
                 }
                 for (int i = 'a'; i <= 'z'; i++) {
                     asciiMarks[i] = LOWERCASE_LETTER_MARK;
                 }
                 for (int i = 'A'; i <= 'Z'; i++) {
                     asciiMarks[i] = UPPERCASE_LETTER_MARK;
                 }
                 asciiMarks[0xff] = EOF;
             }
         }
     }
 }

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

  1. cf 853 D Michael and Charging Stations [dp]

    题面: 传送门 思路: 看到题目,第一思路是贪心,但是我很快就否决掉了(其实分类贪心也可以做) 然后就想,贪心不能解决的状态缺失,是否可以用dp来解决呢? 事实证明是可以的 我们设dp[i][j]表示 ...

  2. 【Codeforces 1110D】Jongmah FST分析

    Codeforces 1110 D FST分析 dotorya.FizzyDavid.MofK.gamegame.matthew99.chokudai.eddy1021.DBradac.Happy_N ...

  3. codeforces:Roads in the Kingdom分析和实现

    题目大意:国家有n个城市,还有n条道路,每条道路连通两个不同的城市,n条道路使得所有n个城市相互连通.现在国家经费不足,要关闭一条道路.国家的不便度定义为国家中任意两个不同的城市之间的距离的最大值,那 ...

  4. CodeForces - 762E:Radio stations (CDQ分治||排序二分)

    In the lattice points of the coordinate line there are n radio stations, the i-th of which is descri ...

  5. 【codeforces 796D】Police Stations

    [题目链接]:http://codeforces.com/contest/796/problem/D [题意] 在一棵树上,保证每个点在距离d之内都有一个警察局; 让你删掉最多的边,使得剩下的森林仍然 ...

  6. DZY Loves Colors CodeForces - 444C (线段树势能分析)

    大意:有$n$个格子, 初始$i$位置的颜色为$i$, 美丽值为0, 有两种操作 将区间$[l,r]$内的元素全部改为$x$, 每个元素的美丽值增加$|x-y|$, $y$为未改动时的值 询问区间$[ ...

  7. Codeforces 1175F 尺取法 性质分析

    题意:给你一个数组,问有多少个区间,满足区间中的数构成一个排列. 思路(大佬代码):我们发现,一个排列一定含有1,所以我们不妨从1开始入手计算构成排列的区间个数.对于每个扫描到的1(假设处于位置i), ...

  8. CodeForces - 158B.Taxi (贪心)

    CodeForces - 158B.Taxi (贪心) 题意分析 首先对1234的个数分别统计,4人组的直接加上即可.然后让1和3成对处理,只有2种情况,第一种是1多,就让剩下的1和2组队处理,另外一 ...

  9. CodeForces 834C - The Meaningless Game | Codeforces Round #426 (Div. 2)

    /* CodeForces 834C - The Meaningless Game [ 分析,数学 ] | Codeforces Round #426 (Div. 2) 题意: 一对数字 a,b 能不 ...

随机推荐

  1. 关于父类私有属性在子类构造函数中super调用的解释

    package test; public class Car { private int carMoney; //汽车租金 private String carName; //汽车名字 private ...

  2. Java例子

    1. 本章学习总结 今天主要学习了三个知识点 封装 继承 多态 2. 书面作业 Q1. java HelloWorld命令中,HelloWorld这个参数是什么含义? 今天学了一个重要的命令javac ...

  3. New Concept English Two 15 37

    listening speaking reading writing and translating $课文35  捉贼! 355. Roy Trenton used to drive a taxi. ...

  4. centos7上systemd详解

    centos7上systemd详解  发表于 2016-06-07 |  分类于 linux CentOS 7继承了RHEL 7的新的特性,例如强大的systemd, 而systemd的使用也使得以往 ...

  5. 转:XML 中的空白字符须知:xml:space

    了解 XML 空白字符的概念并掌握如何避免与之相关的问题的技巧. 2006 年 4 月发布 很多时候,您可能都没注意到,在 XML 中所做的更改影响着您访问 XML 文档中数据的方式.例如: < ...

  6. 1月中旬值得一读的10本技术新书(机器学习、Java、大数据等)!

    1月中旬,阿里云云栖社区 联合 博文视点 为大家带来十本技术书籍(机器学习.Java.大数据等).以下为书籍详情,文末还有福利哦! 书籍名称:Oracle数据库问题解决方案和故障排除手册 内容简介 & ...

  7. Linux运维学习笔记-文件权限知识总结

    权限总结: 1. 可读r:表示具有读取\阅读文件内容的权限: 2. 可写w:表示具有新增.修改文件内容的权限: a. 若果没有r配合,那么vi编辑文件会提示无法编辑(但可以强制编辑),echo可以重定 ...

  8. 重新学习之spring第四个程序,整合struts2+hibernate+spring

    第一步:导入三大框架的jar包(struts2.3.16.1+hibernate3.2+spring3.2.4) 第二步:编写web.xml 和struts.xml和applicationContex ...

  9. redis的maxmemory与maxmemory-policy关系

    如果redis配置了maxmemory和maxmemory-policy策略,则当redis内存数据达到maxmemory时,会根据maxmemory-policy配置来淘汰内存数据,以避免OOM.r ...

  10. vue2.0实现一个模态弹框,内容自定义(使用slot)

    定义模态框:合理使用插槽 model.vue <!-- 模态弹窗 --> <template> <div class="self-modal" v-s ...