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;
}
重要的是自信,一旦有了自信,人就会赢得一切。