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
*/