寒假思维训练day9 带DP的问题怎么看出来

CF:   Problem - 455A - Codeforces


一、有时候要要看出来是个dp其实不太简单,会用DP这个工具其实是不够的,还得在不同的应用场景去知道要用DP(动态规划)解决问题,稍后会有例题。

1、先抽象问题,看看问题能否通过贪心得来,无法贪心意味着只能暴力,DP实际就是优雅的暴力。

2、问题中的状态的转移以及变化的规律情况是否都是固定的几种,而不是一直会改变,没法确定,例如拿与不拿,删与不删都是固定的两种状态,确定而且可分。

3、状态的转移是否能覆盖所有的个体。

4、时间复杂度分析,维数的乘积是否过得去。

二、给出几个我这几日来修炼构造题得到的一些规律和总结附带例题,并且会在后续更新更多这些题目的题解和总结: 

1、前后缀贪心,比如说观察前后缀的sum,去看以后怎么考虑最好。Problem - 1903C - Codeforces

2、双指针贪心法,考虑两端相消或者相互作用,还有就是考虑左右边界。   Problem - 1891C - Codeforces

Problem - 1907D - Codeforces

3、转换观察法,有些关系可以抽象成图,观察图的某些性质去总结规律。也可以抽象成一个集合,两个集合相等可以说明有解可构造。Problem - 1891C - Codeforces

4、打表找规律,一般没什么规律可循即可打表找规律,一般和数论有关的很喜欢考,acm也喜欢考,属于人类智慧题。Problem - 1916D - Codeforces

5、公式推导演算,常见的分为公式的等价变形、公式的化简(这个常考,一般需要先证明某些性质,可以直接抵消,一般如果原公式处理起来很复杂时就可以考虑)。Problem - 1889B - Codeforces

6、奇偶讨论法:考虑奇偶数去简化问题或者分类问题,从其中的一些运算性质入手,因为奇数偶数的加减以及%运算(这个结论很重要)的结果的奇偶性是固定的,Problem - 1898C - Codeforces

7、性质构造法:根据性质构造模型,看看能不能分成几个块,几个不同的集合,再选择算法去解决。Problem - 1873G - Codeforces

8、顺序处理法:考虑从小到大处理,或者是从大到小处理,有时候先处理小的对大的不会有影响,或者反过来,这样的处理顺序是最完美的。Problem - 1904D2 - Codeforces

9、边界贪心法,一般要在问题的最边界处考虑,有时候这样做结果是最优的,或者考虑边界上的影响,假如让影响最小,就使得影响<= 固定值 。 ​​​​​​Problem - E - Codeforces and Problem - 1903C - Codeforces


三、day9(思维 + DP,模型:性质构造法)Problem - 455A - Codeforces

题意:
给定一个长度为N(1 <= N <= 1e5)的序列a,元素a[i]满足1 <= a[i] <= 1e5,每次可以进行以下操作:选择目标a[i], 则可以使得分ans += a[i], 并且删除队伍中所有值为a[i] - 1,a[i] + 1的值。
题解:
对于这个问题,显然需要排序然后进行分块(将值相同的看成是一个部分),首先考虑贪心,先从后方开始贪心,显然每次选最大的,假设这种情况sum(a[i]) <= sum(a[i] - 1),但是sum(a[i] - 1) < sum(a[i] - 2), 这样实际上就会有很多种情况,很显然无法去贪心,如果不能贪心,就尝试去暴力,我们发现对前面的每个数字操作的时候符合我在前面说过的那些步骤,是可以用f[i][0 / 1]去表示状态的,f[i][1]:表示选择了a[i]这个数, f[i][0]:表示未选择。状态转移见代码  

代码:就是DP,并且分类讨论的过程。

#include 
#define int long long 
using namespace std;
const int N = 1e6 + 10; 
int n; 
int a[N]; 
int cnt[N]; 
int f[N][2];  
namespace Calc {}
void solve() {
    cin >> n;
    for(int i = 1; i <= n; i ++ ) cin >> a[i], cnt[a[i]] = 0;
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= n; i ++ ) cnt[a[i]] ++; 
    vector us;
    us.push_back(-1);
    for(int i = 1; i <= n; i ++ ) {
        int j = i; 
        while(j + 1 <= n && a[j + 1] == a[i]) ++ j; 
        us.push_back(a[i]);
        i = j;    
    }
    memset(f, -0x3f, sizeof f); 
    f[0][0] = 0; 
    // cout << us.size() << endl; 
    
    for(int i = 1; i < us.size(); i ++ ) {
        if(us[i] - us[i - 1] == 1) {
            f[i][1] = f[i - 1][0] + us[i] * cnt[us[i]]; 
            f[i][0] = max(f[i - 1][0], f[i - 1][1]); 
        }
        else {
            f[i][1] = max(f[i - 1][0], f[i - 1][1]) + us[i] * cnt[us[i]]; 
            f[i][0] = max(f[i - 1][0], f[i - 1][1]); 
        }
    }
    cout << max(f[us.size() - 1][1], f[us.size() - 1][0]) << endl; 
}
signed main() {
    int ts = 1;
    // cin >> ts;
    while(ts --) solve();
    
    return 0;
}

你可能感兴趣的:(算法)