2021江苏省赛

C. Magical Rearrangement

题意:
给定10个数字,分别代表0~9有多少。求这些数字组成的最小的数,满足:
1.任意相邻的数不相等
2.没有前导0(单个的0可以)
做法:
先不考虑最小,如果最后构造出来了这个串,那么这个串的子串也一定是合法的。即:当前已经构造好了这个串的前一部分,准备要将数字i添加到后面,如果添加之后,剩余的数字能合法的构造,那么才可以添加这个数字。
至于能不能合法的构造,考虑最坏的情况,即某一数字出现次数最多,为sum1,其他数字出现次数的和加起来,为sum2,如果sum1 > sum2 +1,那么就不合法了。
剩下的都是一些小细节,很容易想到。
注意特判单个的0就好了。
核心思想就是整个合法,那么子串也一定合法。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1000 + 5, mod = 1e9 + 7;
int a[N];
bool check()
{
    int ret = 0;
    for (int i = 0; i <= 9; i++) {
        if (a[i]) ret++;
    }
    return ret >= 1;
}
signed main ()
{
    int tt;
    cin >> tt;
    while(tt--){
        bool ok = 1;
        int kk = 0;
        for (int i = 0; i <= 9; i++) {
            cin >> a[i];
            kk += a[i];
        }
        if (kk == 1){
            if (a[0] == 1) {
                cout << 0 << endl;
                continue;
            }
        }
        string s;
        while(check())
        {
            int sum = 0;
            for (int i = 0; i <= 9; i++) sum += a[i];
            bool f = 0;
            for (int i = 0; i <= 9; i++) {
                if (!a[i]) continue;
                if (s.empty() && i == 0) continue;
                char ch = i + '0';
                if (s.back() == ch) continue;

                int pos = 10;
                for (int j = 0; j <= 9; j++) {
                    if (j == i) {
                        if (a[j] - 1 > a[pos]) pos = j;
                        continue;
                    }
                    if (a[j] > a[pos]) pos = j;
                }

                if (pos == i) {
                    int re = sum - a[i];
                    if (a[i] - 1 <= re) {
                        a[i]--;
                        s += ch;
                        f = 1;
                        break;
                    }
                }
                else {
                    int re = sum - a[pos] - 1;
                    if (a[pos] <= re + 1) {
                        a[i]--;
                        s += ch;
                        f = 1;
                        break;
                    }
                }
            }
            if(!f) {
                ok = 0;
                break;
            }
        }
        if (!ok) puts("-1");
        else cout << s << endl;
    }
    return 0;
}

J. Anti-merge

题意:
给定n*m的表格,在这张表中,上下两个数相同,那么这两个数合并,左右相同,那左右再合并一次。现在可以给这些数打上标记,如果两个数相同但是标记不同,那他们不能合并。求打上标记种类最少的时候,标记的最小数目,以及哪些地方要打标记。
做法:
很明显相同的数字构成一个连通块,只要联通,那么上下和左右就可以有合并的操作。想要标记种类最少,一个标记就ok了。阻断所有的联通路径,那么就是bfs染色,由bfs扩展开的区域,染上不同于当前点的颜色。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 500 + 5, mod = 1e9 + 7;
int mp[N][N];
int f[N][N];
int nex[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int n, m;
int a[N * N];
int tot;
int s[N][N];
struct node {
    int x, y;
};
int bfs(int ax, int ay)
{
    queue<node> q;
    q.push(node{ax, ay});
    f[ax][ay] = 1;
    s[ax][ay] = ++tot; //tot是第几号连通块
    int ans1 = 1, ans2 = 0;
    // cout << ax << " " << ay << "---------" << endl;
    while(!q.empty())
    {
        int x = q.front().x, y = q.front().y;
        q.pop();

        for (int i = 0; i < 4; i++) {
            int tx = x + nex[i][0], ty = y + nex[i][1];
        
            // cout <<"I am " << tx << " " << ty << ": " << endl;
            // cout << f[tx][ty] << " " << mp[tx][ty] << " " << mp[ax][ay] << endl;
            
            
            if (f[tx][ty] == 0 && tx <= n && ty <= m && mp[tx][ty] == mp[ax][ay])
            {
                if (f[x][y] == 1)
                {
                    f[tx][ty] = -1;
                    ans2++;
                }
                else
                {
                    f[tx][ty] = 1;
                    ans1++;
                }
                q.push(node{tx, ty});
                s[tx][ty] = tot;
            }
        }
    }
    if (ans1 < ans2) {
        a[tot] = 1; //当前点f标记若为次则输出
    }
    else {
        a[tot] = -1;
    }
    // cout<< "---------" << endl;
    return min(ans1, ans2);
}
signed main ()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) cin >> mp[i][j];
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++){
            if (f[i][j] == 0) {
                ans += bfs(i, j);
            }
        }
    }
    // for (int i = 1; i <= n; i++) {
    //     for (int j = 1; j <= m; j++){
    //         cout << f[i][j] << " ";
    //     }
    //     cout << endl;
    // }
    int kk = 0;
    if (ans) kk = 1;
    cout << kk << " " << ans << endl;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++){
            int fa = s[i][j];
            if (a[fa] == f[i][j]){
                printf("%lld %lld 1\n", i, j);
            }
        }
    }
    return 0;
}

H. Reverse the String

题意:
给定字符串s,可以进行一次翻转区间的操作。求翻转之后,字典序最小的s。(可以不反转)
做法:
题面简单,代码量却极大。很明显如果一个字符小于他后面出现过的的字符,那么就要从此刻开始翻转。
比如,当前是b,后面出现了k个a,那么就要考虑把哪个a翻转上来。
直接考虑翻转后,b前面的不用考虑了,翻转后会得到k个长度相同的a开头的字符串,判定哪个字符串字典序最小即可。用二分+哈希判定比较简单。
所以要处理一下前缀哈希和后缀哈希。
剩下的都是一些细节了,不用特判什么。主要看代码功底了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e6 + 5, mod = 1e9 + 7;
char s[N], hou[N];
typedef unsigned long long ull;
ull p[N], fr[N], fl[N];
vector<int> v;
ull get_hou(int l, int r)
{
    int len = r - l + 1;
    return fr[l] - fr[r + 1] * p[len];
}
ull get_qian(int l, int r)
{
    int len = r - l + 1;
    return fl[r] - fl[l - 1] * p[len];
}
int pos;
bool check(int mid, int pos, int pos1, int pos2)
{
    ull h1, h2;
    if (mid <= pos1 - pos + 1) h1 = get_hou(pos1 - mid + 1, pos1);
    else {
        h1 = get_hou(pos, pos1);
        h1 = h1 * p[mid - (pos1 - pos + 1)] + get_qian(pos1 + 1, pos1 + mid - (pos1 - pos + 1));
    }

    if (mid <= pos2 - pos + 1) h2 = get_hou(pos2 - mid + 1, pos2);
    else {
        h2 = get_hou(pos, pos2);
        h2 = h2 * p[mid - (pos2 - pos + 1)] + get_qian(pos2 + 1, pos2 + mid - (pos2 - pos + 1));
    }

    return h1 == h2;
}
char get_ch(int start, int pos, int len)
{
    if (start - len + 1 < pos) {
        len -= start - pos + 1;
        return s[start + len];
    }
    else return s[start - len + 1];
}
signed main ()
{
    p[0] = 1;
    for (int i = 1; i <= 1500005; i++) p[i] = p[i - 1] * 131;
    int tt;
    cin>>tt;
    while(tt--){
        scanf("%s", s + 1);
        int n = strlen(s + 1);
        for (int i = n; i >= 1; i--) {
            fr[i] = fr[i + 1] * 131 + (s[i] - 'a' + 1);
        }
        for (int i = 1; i <= n; i++) fl[i] = fl[i - 1] * 131 + (s[i] - 'a' + 1);
        hou[n] = s[n];
        for (int i = n - 1; i >= 1; i--) {
            hou[i] = min(s[i], hou[i + 1]);
        }
        for (int i = 1; i < n; i++){
            if (hou[i + 1] < s[i]) {
                pos = i;
                for (int j = i + 1; j <= n; j++){
                    if (s[j] == hou[i + 1]) {
                        v.push_back(j);
                    }
                }
                break;
            }
        }
        if (v.empty()) {
            puts(s + 1);
            continue;
        }
        int pos1 = 0;
        int len = v.size();
        for (int i = 1; i < len; i++){
            int l = 1, r = n - pos + 1;
            while(l < r){
                int mid = (l + r + 1)>>1;
                if (check(mid, pos, v[pos1], v[i])) l = mid;
                else r = mid - 1;
            }
            if (l != n - pos + 1) {
            // cout << "???" << endl;
                char ch1 = get_ch(v[pos1], pos, l + 1);
                char ch2 = get_ch(v[i], pos, l + 1);
                // cout << l << endl;
                // cout << ch1 << " " << ch2 << endl;
                if (ch1 > ch2) pos1 = i;
            }
        }
        // cout << pos1 << endl;
        reverse(s + pos, s + v[pos1] + 1);
        puts(s + 1);
        v.clear();
        for (int i = 1; i <= n; i++){
            hou[i] = s[i] = s[0];
            fl[i] = fr[i] = 0;
        }
    }
    return 0;
}
/* 
1
aaaabakkkkaba
 */
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值