Codeforces Round 854 by cybercats (Div. 1 + Div. 2)部分题解

Codeforces Round 854 by cybercats (Div. 1 + Div. 2)

C. Double Lexicographically Minimum

题意:给你一个字符串 s ,你可以重排 s 中的字母而得到字符串 t 。定义 t_{max} 为 t 和 rev(t)rev(t) 是 t 前后翻转得到的字符串)中字典序较大者。现在你需要让 t_{max} 的字典序最小,并输出t_{max} 。

题解:

考虑构造,显然可以从小到大枚举 s 的所有字符,设当前枚举到 x

1、若 x 还剩下至少两个,则将答案的前缀和后缀各加上 x

2、若除 x 外只剩下一种字符 y ,设其出现次数为 k ,则将前缀加上 \left \lceil \frac{k}{2} \right \rceil 个 y ,加上 x ,再加上\left \lfloor \frac{k}{2} \right \rfloor 个 y

3、否则将剩下的、不包括 x 的所有字符按字典序加入前缀,再加入 x

具体代码如下:

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#define int long long
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define all(x) x.begin(), x.end()
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<LL, LL> PLL;
typedef pair<int, int> PII;
const int N = 100010, M = 5010;
const int Mod = 1e9 + 7;
const int INF = 1e18;
 
int n, T, m, k;
string a;
char b[N];
int tmp[30];

void solve()
{
    memset(tmp, 0, sizeof tmp);
    cin >> a;
    n = a.length();
    
    for (auto i : a) tmp[i - 'a'] ++ ;
    
    int backup = -1;
    int l = 0, r = n - 1;
    for (int i = 0; i < 26; i ++ )
    {
        while (tmp[i] > 1)
        {
            b[l ++ ] = b[r -- ] = i + 'a';
            tmp[i] -= 2;
        }
        if (tmp[i])
        {
            backup = i;
            break;
        }
    }
    
    if (~backup)
    {
        int f = 0;//置1为后面只剩v一个字母,置0反之
        for (int i = backup + 1; i < 26; i ++ )
        {
            if (tmp[i])
            {
                if (tmp[i] + 1 == r - l + 1) f = 1;
                break;
            }
        }
        
        if (f)
        {
            for (int i = 0; i < 26; i ++ )
                while (tmp[i] > 1)
                    b[l ++ ] = b[r -- ] = i + 'a', tmp[i] -= 2;
        }
        
        b[r -- ] = backup + 'a', tmp[backup] -- ;
        
        for (int i = 0; i < 26; i++)
            while (tmp[i])
                b[l ++ ] = i + 'a', tmp[i] -- ;
    }
    
    for (int i = 0; i < n; i ++ ) cout << b[i];
    cout << endl;
}

signed main()
{
//    quick_cin();
    cin >> T;
//    getchar();
//    T = 1;

    while (T -- )
    {
        solve();
    }
    
    return 0;
}

D1. Hot Start Up (easy version)

题意:你有两个 CPU,n 个程序要运行,总共 k 种程序需要运行。程序按顺序进行,每次执行程序如果你选择的 CPU 上一次运行的程序不是你当前运行的程序,则需要花费 cold_{i} 的时间。否则,需要花费 hot_{i}​ 的时间。

题解:在本题当中按照题意本应该定义 f[i][j][k]  表示放完第 i 个物品并且第一条序列末尾是 j 并且第二条序列末尾是 k 时最小的花费,这样会变成 O(n^{3}) 的时间复杂度,但是放完第 i 个物品时就可以知道其中一个的末尾元素了,即a[i] ,因此数组变成 f[i][j] ,表示放完第 i 个物品后其中一条序列后另一条子序列的结尾是 j 时的最小花费。

此时的状态转移方程:

第一种——放在第 i-1 个元素的后面

f[i][j] = min(f[i][j], f[i - 1][j] + (a[i] == a[i - 1] ? hot[a[i]] : cold[a[i]]));

第二种——不放在第 i-1 个元素的后面,此时另一条自序列的末尾肯定是 a[i-1]

f[i][a[i - 1]] = min(f[i][a[i - 1]], f[i - 1][j] + (a[i] == j ? hot[a[i]] : cold[a[i]]));

具体代码如下:

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#define int long long
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define all(x) x.begin(), x.end()
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<LL, LL> PLL;
typedef pair<int, int> PII;
const int N = 5010, M = 5010;
const int Mod = 1e9 + 7;
const int INF = 1e18;
 
int n, T, m, k;
int res;
int f[N][N];//表示放完第i个物品并且另一个CPU最后程序是j的最小花费
int a[N], hot[N], cold[N];

void solve()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    for (int i = 1; i <= k; i ++ ) cin >> cold[i];
    for (int i = 1; i <= k; i ++ ) cin >> hot[i];
    
    for (int i = 0; i <= n; i ++ )
            for (int j = 0; j <= k; j ++ )
                f[i][j] = INF;
    
    f[1][0] = cold[a[1]];
    for (int i = 2; i <= n; i ++ )
    {
        int last = a[i - 1];
        for (int j = 0; j <= k; j ++ )
        {
            //放在第i-1个程序后面
            f[i][j] = min(f[i][j], f[i - 1][j] + (a[i] == last ? hot[a[i]] : cold[a[i]]));
            //不放在第i-1个程序后面
            f[i][last] = min(f[i][last], f[i - 1][j] + (a[i] == j ? hot[a[i]] : cold[a[i]]));
        }
    }
    
    res = INF;
    for (int i = 0; i <= k; i ++ )
        res = min(res, f[n][i]);
    
    cout << res << endl;
}

signed main()
{
    quick_cin();
    cin >> T;
//    getchar();
//    T = 1;

    while (T -- )
    {
        solve();
    }
    
    return 0;
}

D2. Hot Start Up (hard version)

题意:在上一题的基础上扩大了 n 和 k 的取值范围,此时 O(nk) 会TLE。

题解:观察之前的二维 DP , f_{i-1}f_{i}​ (此时反过来好思考一点)之间的转移也分为两种情况(与之前的两个情况相对应):

1、对于所有的 f_{i,j} 等于 f_{i-1,j} 加上 cold_{a[i]} 或者 hot_{a[i]} (由 a[i] 是否等于 a[i-1] 决定);

2、此外,对于f_{i, a[i-1]} 还可以取 min(f_{i-1}+cold_{a[i]},f_{i-1,a[i]}+hot_{a[i]}) 。

具体代码如下:

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#define int long long
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define all(x) x.begin(), x.end()
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<LL, LL> PLL;
typedef pair<int, int> PII;
const int N = 300010, M = 5010;
const int Mod = 1e9 + 7;
const int INF = 1e18;
 
int n, T, m, k;
int res;
int a[N], hot[N], cold[N];

struct Node
{
    int l, r;
    int v;  // 区间[l, r]中的最小值
    int add;
}tr[N * 4];

void pushup(int u)
{
    tr[u].v = min(tr[u << 1].v, tr[u << 1 | 1].v);
}

void pushdown(int u)
{
    auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
    if (root.add)
    {
        left.add += root.add, left.v += root.add;
        right.add += root.add, right.v += root.add;
        root.add = 0;
    }
}

void build(int u, int l, int r)
{
    tr[u] = {l, r};
    if (l == r)
    {
        tr[u].v = INF;
        return;
    }
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
    pushup(u);
}

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].v;
    
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    int v = INF;
    if (l <= mid) v = query(u << 1, l, r);
    if (r > mid) v = min(v, query(u << 1 | 1, l, r));

    return v;
}

void modify(int u, int p, int val)//单点取min
{
    if (tr[u].l == p && tr[u].r == p)
    {
        tr[u].v = min(tr[u].v, val);
        return;
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (mid >= p) modify(u << 1, p, val);
        else modify(u << 1 | 1, p, val);
        pushup(u);
    }
}

void modify(int u, int l, int r, int d)//区间加
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        tr[u].v += (LL)(tr[u].r - tr[u].l + 1) * d;
        tr[u].add += d;
    }
    else    // 一定要分裂
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, d);
        if (r > mid) modify(u << 1 | 1, l, r, d);
        pushup(u);
    }
}

void solve()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    for (int i = 1; i <= k; i ++ ) cin >> cold[i];
    for (int i = 1; i <= k; i ++ ) cin >> hot[i];
    
    build(1, 0, k);
    res = INF;
    
    modify(1, 0, 0);
    
    for (int i = 1; i <= n; i ++ )
    {
        res = min(query(1, 0, k) + cold[a[i]], query(1, a[i], a[i]) + hot[a[i]]);//区间查询
        modify(1, 0, k, a[i] == a[i - 1] ? hot[a[i]] : cold[a[i]]);//区间加
        modify(1, a[i - 1], res);//单点修改
    }
    printf("%lld\n", query(1, 0, k));

}

signed main()
{
    quick_cin();
    cin >> T;
//    getchar();
//    T = 1;

    while (T -- )
    {
        solve();
    }
    
    return 0;
}

E. City Union

题意:给你一个 n × m 的网格图,每个点为 '#' 或 '.'。定义一个城市为一个 '#' 的连通块。一开始原图中有两个城市,要求你用最少次将某个格子变为 '#' 的操作使得图中只有一个城市,满足其中任意两个 '#' 的只经过 '#' 的最短路长度为它们的曼哈顿距离(在两点 (a, b) 和 (c, d) 之间的曼哈顿距离为 \left | a-c \right | + \left | b-d \right | )。

题解:

首先考虑满足第二个条件,发现如果有两个 '#' 之间的最短路会绕成 C 形则不满足。于是发现每一行每一列都必须为一段连续的 '#',否则断开的两段中任意两点的最短路不满足要求。

于是需要将每一行每一列填满成一个连续段。

然后还有可能是两个城市,我们需要将它们连在一起,应该各取一个坐标为已填了的点中最大(小)横坐标和最大(小)纵坐标的点,随便连一条最短路,然后再填满。

可以发现这样做需要填的格子数量是最小的。

具体代码如下:

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#define int long long
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define all(x) x.begin(), x.end()
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<LL, LL> PLL;
typedef pair<int, int> PII;
const int N = 110, M = 5010;
const int Mod = 1e9 + 7;
const int INF = 1e18;
 
int n, T, m, k;
int res;
multiset<int> S;
char dist[N][N];
int l[N], r[N];

void solve()
{
    S.clear();
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )
    {
        l[i] = 51, r[i] = 0;
        for (int j = 1; j <= m; j ++ )
        {
            cin >> dist[i][j];
            if (dist[i][j] == '#')
            {
                r[i] = j, S.insert(j);
                if (l[i] > 50) l[i] = j;
            }
        }
    }
    
    for (int i = 1; i <= n; i ++ )
        if (l[i] < 51)
        {
            for (int j = l[i]; j <= r[i]; j ++ )
            {
                if (dist[i][j] == '.')
                    dist[i][j] = '#';
                else
                    S.erase(S.find(j));
            }
            
            if (!S.empty())
            {
                if (*S.begin() < l[i])//防止产生向左的谷底
                    l[i + 1] = min(l[i + 1], l[i]);
                else//防止两个联通块之间不连接
                    l[i + 1] = min(r[i], *S.begin());
                
                if (*S.rbegin() > r[i])//防止产生向右的谷底
                    r[i + 1] = max(r[i + 1], r[i]);
                else//防止两个联通块之间不连接
                    r[i + 1] = max(l[i], *S.rbegin());
            }
        }
    
    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j <= m; j ++ )
            cout << dist[i][j];
        cout << endl;
    }
}

signed main()
{
    quick_cin();
    cin >> T;
//    getchar();
//    T = 1;

    while (T -- )
    {
        solve();
    }
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值