2018 Multi-University Training Contest 5

传送门

B - Beautiful Now

题意:
给定\(n,k\leq 10^9\),现在可以执行至多\(k\)次操作,每次操作交换两个位置上面的数。
输出最后最小的数和最大的数,不能有前导零。

思路:
\(n\)最多为10位数,显然如果\(k\geq 10\),我们直接贪心进行排序然后输出即可(注意前导零)。
下面就考虑\(k<10\)的情况。
我们考虑枚举所有的全排列,对于一个\(1\)~\(n\)的排列,我们将其变为\(1,2,\cdots,n\),最小的交换次数显然是沿着置换走,那么就沿着置换进行交换就行,这一步复杂度为\(O(位数)\)
队友想了个贪心的解法,假设我们当前要得到字典序最小,我们就枚举后面所有当前最小的并进行交换,然后依次进行下去,这一步复杂度为\(O(9!)\)。然后有一个优化:如果当前位数就为最小的那个数,就直接进入下一位。这样可能就会比较快了。
代码如下:

Code
/*
 * Author:  heyuhhh
 * Created Time:  2020/5/7 14:02:54
 */
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
  #define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
  void err() { std::cout << std::endl; }
  template<typename T, typename...Args>
  void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
  template <template<typename...> class T, typename t, typename... A> 
  void err(const T <t> &arg, const A&... args) {
  for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
  #define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5;

int a[10], b[10], pos[10], vis[10];

void run() {
    int n, k;
    cin >> n >> k;
    int tot = 0;
    while (n) {
        a[tot++] = n % 10;
        n /= 10;
    }
    reverse(a, a + tot);
    if (k >= tot) {
        sort(a, a + tot);
        for (int i = 0; i < tot; i++) {
            if (a[i] != 0) {
                cout << a[i];
                break;
            }
        }
        for (int i = 0; i < tot; i++) {
            if (a[i] == 0) {
                cout << 0;
            }
        }
        int cnt = 0;
        for (int i = 0; i < tot; i++) {
            if (a[i] != 0 && cnt) cout << a[i];
            if (a[i] != 0) ++cnt;
        }
        cout << ' ';
        reverse(a, a + tot);
        for (int i = 0; i < tot; i++) cout << a[i];
        cout << '\n';
        return;
    }
    for (int i = 0; i < tot; i++) b[i] = i;
    int ans1 = 2e9, ans2 = -1;
    do {
        if (a[b[0]] == 0) continue;
        for (int i = 0; i < tot; i++) {
            pos[b[i]] = i;
            vis[i] = 0;
        }
        int t = 0;
        for (int i = 0; i < tot; i++) if (!vis[i]) {
            int x = i, len = 0;
            while (!vis[x]) {
                vis[x] = 1;
                x = pos[x];
                ++len;
            }
            t += len - 1;
            if (t > k) break;
        }
        if (t > k) continue;
        int res = 0;
        for (int i = 0; i < tot; i++) {
            res = res * 10 + a[b[i]];
        }
        ans1 = min(ans1, res);
        ans2 = max(ans2, res);
    } while(next_permutation(b, b + tot));
    cout << ans1 << ' ' << ans2 << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    int T; cin >> T; while(T--)
    run();
    return 0;
}

G - Glad You Came

题意:
随机生成\(m\)组询问\((l_i,r_i,v_i)\),然后对序列\([l_i,r_i]\)区间上的数取max。\(m\leq 10^6,n\leq 10^5\)
最后输出\(\displaystyle\oplus_{i=1}^ni\cdot a_i\),其中\(\oplus\)为异或。

思路:
因为数据是随机生成,所以可以直接通过线段树+减枝解决。
当然这个题有一个更巧妙的做法,注意到如果两个询问区间相同,我们可以直接取值最大的那个区间。
那么我们将一组询问的区间\([l_i,r_i]\)拆分为两个长度为\(2\)的次幂的区间,因为总共有\(n\)个数,所以一共有\(O(nlogn)\)个区间。之后我们将所有这样的区间下放,每次长度不断减小一半并合并相同区间,复杂度为\(O(nlogn)\)
代码如下:

Code
/*
 * Author:  heyuhhh
 * Created Time:  2020/5/8 11:15:33
 */
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
  #define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
  void err() { std::cout << std::endl; }
  template<typename T, typename...Args>
  void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
  template <template<typename...> class T, typename t, typename... A> 
  void err(const T <t> &arg, const A&... args) {
  for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
  #define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5, M = 5e6 + 5;

unsigned int X, Y, Z;
unsigned int rng61() {
	X ^= X << 11;
	X ^= X >> 4;
	X ^= X << 5;
	X ^= X >> 14;
	unsigned int tmp = X ^ Y ^ Z;
	X = Y;
	Y = Z;
	Z = tmp;
	return Z;
}

int lg[N];
void init() {
    lg[2] = 1;
    for (int i = 3; i < N; i++) lg[i] = lg[i >> 1] + 1;
}

int n, m;
int f[N][20];

void upd(int &x, int y) {
    if (x < y) x = y;
}

void run() {
    cin >> n >> m >> X >> Y >> Z;
    for (int i = 1; i <= n; i++) 
        for (int j = 0; j < 20; j++) 
            f[i][j] = 0;
    while (m--) {
		int L = rng61() % n + 1, R = rng61() % n + 1, v = rng61() & ((1 << 30) - 1);
        if (L > R) swap(L, R);
        int d = lg[R - L + 1];
        upd(f[L][d], v), upd(f[R - (1 << d) + 1][d], v);
    }
    for (int j = 19; j >= 1; j--) {
        for (int i = 1; i + (1 << (j - 1)) <= n; i++) {
            upd(f[i][j - 1], f[i][j]);
            upd(f[i + (1 << (j - 1))][j - 1], f[i][j]);
        }
    }
    ll ans = 0;
    for (int i = 1; i <= n; i++) {
        ans ^= 1ll * i * f[i][0];
    }
    cout << ans << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    init();
    int T; cin >> T; while(T--)
    run();
    return 0;
}

H - Hills And Valleys

题意:
给出一个长度为\(n,n\leq 10^5\)的字符串,每个位置为\(0\)~\(9\)中的一个数。
现在可以翻转一段区间,问翻转哪一段区间,可以使得该字符串的最长不下降子序列最长。

思路:

  • 考虑如果不存在翻转操作,其实就是一个比较简单的\(dp\)\(dp_{i,j}\)表示匹配了前\(i\)个字符,最后一个为\(j\)的方案数。但这个\(dp\)在有翻转操作存在时就不好处理,因为\(dp\)的第二维我们不好去转移。
  • 我们可以将这个问题转化一下,就题目给定一个匹配串,然后会有一个模式串\(012...9\),现在两个串进行匹配,注意模式串中一个字符可以匹配多次,问最长公共上升子序列。
  • 转移的话\(dp_{i,j}=max\{dp_{i-1,j}+same(i,j),dp_{i,j-1}\}\)
  • 那怎么处理翻转操作呢?这个时候其实就很好处理了,我们只需要翻转模式串即可,最后就形如\(012...xy(y-1)...x(y+1)(y+2)...9\)这个样子,然后类似求最长公共子序列即可。
  • 因为我们还需要求翻转区间,所以我们还要记录一下状态中第一个\(y\)出现的位置,最后一个\(x\)出现的位置即可。

说点题外话:感觉这里就有点类似\(B\)题枚举排列时,我们用一个\(c\)数组来表示排列的下标,我们并不直接枚举原串的全排列,只需要枚举\(c\)就行。这里也相当于用一个数组来表示第二维\(j\)的一个顺序。
细节见代码:

Code
/*
 * Author:  heyuhhh
 * Created Time:  2020/5/8 11:54:49
 */
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
  #define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
  void err() { std::cout << std::endl; }
  template<typename T, typename...Args>
  void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
  template <template<typename...> class T, typename t, typename... A> 
  void err(const T <t> &arg, const A&... args) {
  for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
  #define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5, M = 13;

int n;
int f[N][M], fl[N][M], fr[N][M];
int a[N], b[M], m;
char s[N];
int ans, l, r;

void dp(int L, int R) {
    for (int i = 0; i <= m; i++) {
        f[0][i] = 0;
        fl[0][i] = fr[0][i] = -1;
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            f[i][j] = f[i - 1][j];
            fl[i][j] = fl[i - 1][j];
            fr[i][j] = fr[i - 1][j];
            if (a[i] == b[j]) {
                ++f[i][j];
                if (a[i] == R && fl[i][j] == -1) fl[i][j] = i;
                if (a[i] == L) fr[i][j] = i;
            }
            if (f[i][j - 1] > f[i][j]) {
                f[i][j] = f[i][j - 1];
                fl[i][j] = fl[i][j - 1];
                fr[i][j] = fr[i][j - 1];
            }
        }
    }
    if (L == 1 && R == 1) {
        l = r = 1;
        ans = f[n][m];
    } else {
        if (f[n][m] > ans && fl[n][m] != -1 && fr[n][m] != -1 && fl[n][m] < fr[n][m]) {
            ans = f[n][m];
            l = fl[n][m], r = fr[n][m];   
        }
    }
}

void run() {
    cin >> n >> (s + 1);
    for (int i = 1; i <= n; i++) {
        a[i] = s[i] - '0';
    }
    m = 0;
    for (int i = 0; i < 10; i++) {
        b[++m] = i;
    }
    dp(1, 1);
    for (int i = 0; i < 10; i++) {
        for (int j = i + 1; j < 10; j++) {
            m = 0;
            for (int k = 0; k <= i; k++) b[++m] = k;
            for (int k = j; k >= i; k--) b[++m] = k;
            for (int k = j; k < 10; k++) b[++m] = k;
            dp(i, j);
        }
    }
    cout << ans << ' ' << l << ' ' << r << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    int T; cin >> T; while(T--)
    run();
    return 0;
}
posted @ 2020-05-09 13:21  heyuhhh  阅读(181)  评论(0编辑  收藏  举报