寒假思维训练day17 C. Equal Frequencies

不知不觉已经过了差不多一个月了,坚持一件事情还是有点收获的,今天更新一道1600的构造。


寒假训练计划day17


摘要:

Part1 题意

Part2 题解 (有数学推导,latex形式)

Part3 代码 (C++版本,有详细注释)

Part4 我对构造题的归纳总结(自己对构造题的浅薄理解还有例题

题目链接(有需自取):Problem - 1781C - Codeforces


Part1 题意

题意:

规定字符串是平衡字符串当且仅当字符串中每个字符的数量相等,给定一个长度为n的字符串S,要求找到一个长度为n, n \leq 1e5的平衡字符串T,使得满足T和S的字符匹配数最多,也就是\sum_{i = 0}^{n - 1} (S[i] == T[i] ? 1 : 0)最大,输出不匹配的数量和构造出来的T。


Part2 题解

题解:

1、已知n,S, 设res为字符串的匹配数量并赋初值0。 

2、我们来观察一下平衡字符串有什么性质,因为在平衡字符串中,每个字符的数量都是相等的,假设我们知道每个字符的数量都是cnt,显然n \% cnt = 0, 1 \leq n / cnt \leq 26, 这两个数本质就是n的约数。所以我们对n分解约数,找到所有满足cnt * hve = n, cnt <= 26的数对。

3、假设我们当前考虑cnt, hve,我们需要求出当前这样分配最多能匹配几个字符,因为每个字符在原字符串中的数量不同,所以我们预处理一个Pair<int, char>序列,他们存的是S中的字符和字符的数量,并且排序,再处理出来一个该序列的前缀和序列,根据这两个序列我们可以开始求最多能匹配几个字符,我们先二分出大于等于hve的位置,再进行分了讨论,具体如下:

// u1是cnt, u2是hve, v是匹配数量     
int u1 = c, u2 = n / c;
int l = 1, r = cnt, v; 
if(u1 <= 26) {
    // 先二分
    while(l < r) {
        int mid = l + r >> 1;
        if(q[mid].ff >= u2) 
            r = mid;
        else 
            l = mid + 1;
    }
    // 分类讨论
    v = -1e18;
    if(q[l].ff >= u2) {
        if(cnt - l + 1 >= u1) 
            v = u1 * u2;
        else 
            v = (cnt - l + 1) * u2 + qs[l - 1] - 
            qs[max(0ll,l - 1 - (u1 - (cnt - l + 1)))];
    }
    else {
        v = qs[cnt] - qs[max(0ll,cnt - u1)];
    }
    // 更新res 
    if(res < v) {
        res = v;
        usu1 = u1, usu2 = u2;
    }
}

最后得到了使得匹配数量res最大的cnt, hve

4、现在我们需要利用cnt,hve逆向构造出T,首先我们对于已经处理出来的Pair<int, char>序列(已排序)的后面cnt个,注意可以会有不足的情况,我们就得补充回去,具体操作如下:

cout << n - res << endl;  // 输出最后不匹配的最小数量
vector us;   
// 先处理在原字符串中的字符 
for(int i = cnt; i >= max(1ll, cnt - usu1 + 1); i--) {    
    us.push_back(q[i].ss); 
    st[q[i].ss - 'a'] = usu2;      
}

// 先将原字符串的字符有st标记的填入
for(int i = 0; i < n; i++) 
    if(st[s[i] - 'a']) { 
        ans[i] = s[i];
        st[s[i] - 'a']--;
        is_use[i] = 1;
    }

// 将后面的补上
for(int i = max(1ll, cnt - usu1 + 1) - 1; i >= 1; i--)
    us.push_back(q[i].ss), st[q[i].ss - 'a'] = usu2;  

// 如果不够再补上一些没用过的字符
for(int i = 0; i <= 25; i++) 
    if(!ot[i]) us.push_back(i + 'a'), st[i] = usu2;  
    
int l = 0;
// 最后从入序列的顺序补全字符串
for(int i = 0; i < n; i++) { 
    while(!st[us[l] - 'a']) 
        l++; 
    if(!is_use[i]) 
        ans[i] = us[l], st[us[l] - 'a']--;
}

for(int i = 0; i < n; i++) 
    cout << ans[i];
cout << endl;

Part3 代码(C++):

#include 
#define int long long 
#define ff first 
#define ss second 
using namespace std;
using PII = pair; 
constexpr int N = 1e6 + 10; 
int n, m, sum; 
int ot[50], qs[50], is_use[N], st[50], cnt; 
PII q[50];
void solve() {
    string s; 
    cin >> n >> s; 
    cnt = 0;
    for(int i = 0; i <= 30; i ++ ) ot[i] = 0, st[i] = 0; 
    for(int i = 0; i < n; i ++ ) ot[s[i] - 'a'] ++, is_use[i] = 0;
    for(int i = 0; i <= 30; i ++ ) if(ot[i]) q[++ cnt] = {ot[i], 'a' + i};
    sort(q + 1, q + 1 + cnt);
    
    for(int i = 1; i <= cnt; i ++ ) qs[i] = q[i].ff + qs[i - 1];
    int res = -1e18;
    vector ans(n + 1, 0); 
    int usu1,usu2;
    for(int c = 1; c <= n / c; c ++ ) 
        if(n % c == 0) {
            int u1 = c, u2 = n / c;
            int l = 1, r = cnt, v; 
            if(u1 <= 26) {
                while(l < r) {
                    int mid = l + r >> 1;
                    if(q[mid].ff >= u2) r = mid;
                    else l = mid + 1;
                }
                v = -1e18;
                if(q[l].ff >= u2) {
                    if(cnt - l + 1 >= u1) v = u1 * u2;
                    else v = (cnt - l + 1) * u2 + qs[l - 1] - qs[max(0ll,l - 1 - (u1 - (cnt - l + 1)))];
                }
                else {
                    v = qs[cnt] - qs[max(0ll,cnt - u1)];
                }
                if(res < v) {
                    res = v;
                    usu1 = u1, usu2 = u2;
                }
            }
            u1 = n / c, u2 = c;
            if(u1 <= 26) {
                l = 1, r = cnt;
                while(l < r) {
                    int mid = l + r >> 1;
                    if(q[mid].ff >= u2) r = mid;
                    else l = mid + 1;
                }
                v = -1e18;
                if(q[l].ff >= u2) {
                    if(cnt - l + 1 >= u1) v = u1 * u2;
                    else v = (cnt - l + 1) * u2 + qs[l - 1] - qs[max(0ll,l - 1 - (u1 - (cnt - l + 1)))];
                }
                else {
                    v = qs[cnt] - qs[max(0ll,cnt - u1)];
                }
                if(res < v) {
                    res = v;
                    usu1 = u1, usu2 = u2;
                }
            }
            
        }
        cout << n - res << endl; 
        vector us;
        for(int i = cnt; i >= max(1ll, cnt - usu1 + 1); i -- ) {
            us.push_back(q[i].ss), st[q[i].ss - 'a'] = usu2;  
            // cout << q[i].ss << endl;
        }

        for(int i = 0; i < n; i ++ ) 
            if(st[s[i] - 'a']) { 
                // cout << s[i] << endl;
                ans[i] = s[i], st[s[i] - 'a'] --, is_use[i] = 1;
                // cout << ans[i] <<  "#";
            }
            
        for(int i = max(1ll, cnt - usu1 + 1) - 1; i >= 1; i -- )
            us.push_back(q[i].ss), st[q[i].ss - 'a'] = usu2;  
        
        for(int i = 0; i <= 25; i ++ ) if(!ot[i]) us.push_back(i + 'a'), st[i] = usu2;  
        
        int l = 0;

        for(int i = 0; i < n; i ++ ) { 
            while(!st[us[l] - 'a']) l ++; 
            if(!is_use[i]) ans[i] = us[l], st[us[l] - 'a'] --;
        }
        
        
        for(int i = 0; i < n; i ++ ) cout << ans[i];
        cout << endl;
}   
signed main() {

    int ts; 
    cin >> ts; 
    while(ts --) 
        solve(); 
    
    return 0; 
}


Part4 我对构造题的归纳总结(自己对构造题的浅薄理解) 

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

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