M - Gambling(cf原题)题解
[原题链接](Problem - 1692H - Codeforces (Unofficial mirror by Menci))
题目大意:
小马最开始手上有 1 块钱,有 n 个色子,每个色子的点数小马都能猜准为 a ( 1 ~ n);现在小马只能在一个连续的回合猜同一个点数,每猜对一次小马手上的钱就会翻倍,问你怎么猜才能让小马手上的钱最多。输出的信息为,小马猜的数 m ,从第 l 个回合开始猜, 第 r 个回合猜完后结束。
**这个题我们只需要去输出一个正确的答案,也就是说我们输出的答案只要是对的,给的例题不一样也不要紧一样能过。
注意这个题的 n 的范围,我们开不了二维DP,所以这里我们需要思考如何用 f ( i ) 去表示这个题的状态方程;
这里采用之前在 01 背包滚动优化的思路,将 f ( i ) 表示在区间 [ 1 ~ i ] 我们猜对第 i 个色子时的最大猜对的次数,如果我们猜对的次数越多最后的钱就越多但是猜错了会扣一次积累的次数;
**这里我的思路是存储次数,当然去存储钱数也可以不过我觉得这个样子更方便一点;
(其实线性DP写多了对题目中的一些条件自然会很敏感了);
分析一下猜对第 i 个色子时有哪些情况:
1、这个色子上的数在之前没有出现过,那么说明了在这之前的色子与我们猜的数不符所以为了让猜对的次数变多我们不能去猜,所以这时我们的区间是 [ i , i ];
**f ( i ) 表示在区间 [ 1 ~ i ] 我们猜对第 i 个色子时的最大猜对的次数,记住猜错会扣一次次数;(第二次强调)
所以此时 f [ i ] = f [ i ];
2、这个色子上的数在之前出现过,这里和之前有些不一样了!!!我们需要考虑上一次出现这个数的色子的后面一位到目前也就是第 i 个色子这个区间!这个区间!这个区间!的猜对次数;
**上一次出现这个数的色子的后面一位!上一次出现这个数的色子的后面一位!上一次出现这个数的色子的后面一位!(别急,慢慢看)
我们需要知道在第 i 个色子前面第 1 个出现相同数的是第几个色子,所以我们需要去开一个数组去存储一下;
我们假设上一个出现的是第 k 个色子,那么在 [ k + 1, i ] 这个区间的猜错的个数为 i - k - 1 ,猜对的个数为 1 ;明确前面的这一串是因为我们要去判断第 k 个色子到第 i 个色子是否可以连起来;
如果不能连起来 f [ i ] = f [ i ] ,反之 f [ i ] = f [ k ] - ( i - k - 1 ) + 1 。
OK现在我们就明确了状态方程:
f [ i ] = max( f [ i ] , f [ k ] - ( i - k - 1 ) + 1 ) ;
明确了状态方程现在我们来考虑初始化 !!!(讲DP不谈初始化就是耍流氓,真觉得状态方程有了就万事大吉了是吧)
如果我们只从第 i 个色子的地方开始猜且只猜这一次,那么此时我们猜的数就是第 i 个色子上的数,且区间为 [ i , i ] ,所以我们需要初始化 f [ i ] = 1 ;
至于记录的区间左右边界我们可以用一个数组去记录,举个栗子:l [ r ] 表示区间 [ l [ r ] , r ] ,同样的这样我们也需要初始化因为开始时长度为 1 所以 l [ i ] = i ;
1 for (ll i = 1; i <= n; i++) 2 { 5 if (vis.count(a[i])) //判断a[i]是否出现过 6 { 7 if (f[i] < f[vis[a[i]]] - (i - vis[a[i]] - 1) + 1) //这里的 vis[a[i]] 相当于上放的 k 8 //这里是我们在上面推出来的状态方程 9 { 10 f[i] = f[vis[a[i]]] - (i - vis[a[i]] - 1) + 1; 11 l[i] = l[vis[a[i]]]; //更新左边界 12 } 13 } 14 vis[a[i]] = i; //记录a[i]最后一次出现的位置 15 if (res < f[i]) //用来判断最优解, res 表示综合猜对和猜错的题数 16 { 17 m = a[i]; //用 m 来存储猜的数字 18 r = i; //用来存储边界 19 res = f[i]; //更新目前的最优解 20 } 21 }
最后输出最优解中猜的数字 m , 左边界 l [ r ] , 右边界 r 。
OK题解到此为止,我认为DP雀实算一个难点,但是谁不知道来ACM是道阻且长的呢。加油吧!!!

浙公网安备 33010602011771号