寒假思维训练计划day16 A. Did We Get Everything Covered?

今天更新一道1月27号晚上div2的C题作为素材,感觉用到了我的构造题总结模型,我总结了一系列的模型和例题。


摘要:

Part1  定义"边界贪心法"

Part2  题意 

Part3  题解

Part4  代码

Part5  思维构造题模型和例题


Part1 边界贪心法(该题所用到的模型):

边界贪心法:对于整体而言,我们去除已经定好的情况,剩下难以确定的情况,我们从其最边缘的位置去考虑、去假设;一般要在问题的最边界处考虑,一般最边界决定总体的走向,后面会给例题以及其它的模型及其例题。 


Part2 题意:Problem - A - Codeforces

题意:

给定n,k,m,再给定一个长度为m的字符串S,该字符串满足只包含前k个小写字母字符, 要求判断能否找到所有的长度为n且字符组成只包含前k个小写字母字符的字符串为S的子序列(删除一些元素后,其余元素顺序不变而组成的序列),能输出"YES",不能输出"NO",并构造出该字符串,它满足不是S的子序列。


Part3 题解:

题解:

1、已知:
n,k,m, S, len(S) = m, S[i] \epsilon ['a', 'a' + k - 1], 1<= n,k <= 26,\\1 <= m <= 1000

2、我们不妨从头到尾遍历,当满足len(set(s[i], s[i + 1], ... , s[j])) = k划分一个块,即块中元素恰好包含了所有的前k个元素,当出现len(set(s[i], s[i + 1], ... , s[j])) < k必然是最后一个块,因为它往后无法得到全部元素,一直到最后才能满足,否则就可以一直往后合并。

3、接下来,我么分好了块,假设有:block_1, block_2, block_3, ..., block_{cnt}表示分出的块,

我们进行分类讨论\left\{\begin{matrix} YES, cnt >= n\\ NO, cnt < n\end{matrix}\right.
3.1 当cnt >= n那我们必然能从每个块分别拿出任意字符,组成长度为n的子序列。
3.2 当cnt < n,此时我们证明一定是无解的,

        3.2.1 首先我们证明第一个命题,所有满足len(set(s[i], s[i + 1], ... , s[j])) = k块的最后一个元素必然只存在一个在当前块中:当最后一个元素有两个,我们必然可以让块的右边界往左边移动,矛盾。

        3.2.2  证明从前往后每次取块中最后一个元素的序列一定是唯一的:已知有block_1, block_2, block_3, ..., block_{cnt},1 <= cnt < n, 其中满足len(set(s[i], s[i + 1], ... , s[j])) = k的是:block_1,block_2,....block_t, t == cnt \quad or \quad t == cnt- 1 \\ string = s_1 + s_2 + s_3 + ... + s_t,当舍弃最后一个块的元素s_t,我们知道该块中仅仅只有一个这样的元素(由3.2.1得), 只能在前面的块找到 s_{t-1} + s_t,显然s_{t-1}也只由一个在块中,所以一直往前,必然无法找到新的可替代的string,所以string唯一。

        3.2.3  所以取每个块的最后一个元素得到的string,len(string) < n,并且它是唯一的,我们只需要在后面补上任意元素,使得len(string)==n就一定不是S的子序列。


Part4 代码(C++) 

C++代码(边界贪心法): 

#include 
#define int long long 
#define ff first 
#define ss second 
using namespace std; 
using PII = pair; 
constexpr int N = 1e6 + 10;
constexpr int inf = 0x3f3f3f3f; 
int n, k, m; 
// int us[N][30], uss[N][30]; 
void solve() {
    cin >> n >> k >> m;
    string s; 
    cin >> s; 
    string ans = ""; 
    int o = 0; 
    for(int i = 0; i < m; i ++ ) {
        set se; 
        se.insert(s[i]); 
        int j = i; 
        while(se.size() < k && j + 1 < m) {
            ++ j; 
            se.insert(s[j]); 
        }
        if(se.size() < k) {
            for(int j = 0; j < k; j ++ )
                if(se.count(('a' + j)) == 0) {
                    ans += ('a' + j); 
                    break; 
                }
        }
        else o ++, ans += s[j]; 
        
        i = j; 
    }
    if(o >= n) cout << "YES" << endl; 
    else {
        cout << "NO" << endl; 
        while(ans.size() < n) ans += 'a'; 
        cout << ans << endl;
    }
}
signed main() {
    ios::sync_with_stdio(false); 
    cin.tie(0); 
    cout.tie(0); 
    int ts; 
    cin >> ts; 
        
    while(ts -- ) solve(); 
    
    return 0;
}

Part5 思维构造题模型和例题

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
Problem - A - Codeforces

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