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通过:
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分析和实现的更多相关文章
- cf 853 D Michael and Charging Stations [dp]
题面: 传送门 思路: 看到题目,第一思路是贪心,但是我很快就否决掉了(其实分类贪心也可以做) 然后就想,贪心不能解决的状态缺失,是否可以用dp来解决呢? 事实证明是可以的 我们设dp[i][j]表示 ...
- 【Codeforces 1110D】Jongmah FST分析
Codeforces 1110 D FST分析 dotorya.FizzyDavid.MofK.gamegame.matthew99.chokudai.eddy1021.DBradac.Happy_N ...
- codeforces:Roads in the Kingdom分析和实现
题目大意:国家有n个城市,还有n条道路,每条道路连通两个不同的城市,n条道路使得所有n个城市相互连通.现在国家经费不足,要关闭一条道路.国家的不便度定义为国家中任意两个不同的城市之间的距离的最大值,那 ...
- 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 ...
- 【codeforces 796D】Police Stations
[题目链接]:http://codeforces.com/contest/796/problem/D [题意] 在一棵树上,保证每个点在距离d之内都有一个警察局; 让你删掉最多的边,使得剩下的森林仍然 ...
- DZY Loves Colors CodeForces - 444C (线段树势能分析)
大意:有$n$个格子, 初始$i$位置的颜色为$i$, 美丽值为0, 有两种操作 将区间$[l,r]$内的元素全部改为$x$, 每个元素的美丽值增加$|x-y|$, $y$为未改动时的值 询问区间$[ ...
- Codeforces 1175F 尺取法 性质分析
题意:给你一个数组,问有多少个区间,满足区间中的数构成一个排列. 思路(大佬代码):我们发现,一个排列一定含有1,所以我们不妨从1开始入手计算构成排列的区间个数.对于每个扫描到的1(假设处于位置i), ...
- CodeForces - 158B.Taxi (贪心)
CodeForces - 158B.Taxi (贪心) 题意分析 首先对1234的个数分别统计,4人组的直接加上即可.然后让1和3成对处理,只有2种情况,第一种是1多,就让剩下的1和2组队处理,另外一 ...
- CodeForces 834C - The Meaningless Game | Codeforces Round #426 (Div. 2)
/* CodeForces 834C - The Meaningless Game [ 分析,数学 ] | Codeforces Round #426 (Div. 2) 题意: 一对数字 a,b 能不 ...
随机推荐
- Office 365 开发 集成VS2013 (一)
博客地址 http://blog.csdn.net/foxdave 题外话:好久不写了,个人比较懒,有时候想写东西的时候想一想就又不知从何下笔了.之前因为某些机缘发现自己完全是个管理外行,所以最近下了 ...
- vue.js 源代码学习笔记 ----- keep-alives
/* @flow */ import { callHook } from 'core/instance/lifecycle' import { getFirstComponentChild } fro ...
- 那些实用的Nginx规则
1. 概述 大家都知道Nginx有很多功能模块,比如反向代理.缓存等,这篇文章总结下我们这些年实际环境中那些有用的Nginx规则和模块,大部分是用法的概括及介绍,具体细节在实际配置时再自行google ...
- hdu 3613 Best Reward
After an uphill battle, General Li won a great victory. Now the head of state decide to reward him w ...
- List用法与介绍
泛型的好处:它为使用c#语言编写面向对象程序增加了极大的效力和灵活性.不会强行对值类型进行装箱和拆箱,或对引用类型进行向下强制类型转换,所以性能得到提高. 性能注意事项:在决定使用ILis ...
- 【转】Notepad++中Windows,Unix,Mac三种格式之间的转换
原文网址:http://www.crifan.com/files/doc/docbook/rec_soft_npp/release/htmls/npp_func_windows_unix_mac.ht ...
- 移植SDL最新版本(转)
原文出自:http://blog.csdn.net/flyyang123456789/article/details/17223485 首先 将所要移植的包准备好 有 SDL2-2.0.1.tar. ...
- Eclipse实用用快捷键
1.ctrl+shift+o 添加必须import并删除无用import.代码被改动时容易产生很多无用引用,此时这个快捷键就可以一次把如下的引用删掉了
- linux系统报错日志学习
linux本身会自动记录系统报错日志:/var/log/messages 这个日志记录,我是在什么时候发现其强大的作用的呢?它有点像我们使用php脚本开发接口的时候技术员在重要地方打日志的效果,方便技 ...
- WeX5 苹果APP打包教程
来源:http://docs.wex5.com/app-packing/ WeX5平台App打包教程 WeX5是前端快速开发框架,可开发跨端运行应用,是移动App/微信/WebApp开发利器,一次开发 ...