May. Personal training 3

1493C - K-beautiful Strings

题意

给定一个长度为n的字符串(只包含小写字母)再给定一个k 规定的字符串中每个字符的个数 求一个字典序最小的字符串满足:长度与给定的字符串相等 字典序不比给定的字符串小 且含有的字母个数都是k的倍数

思路

我们可以从后往前枚举要更改的字母 即先固定前面若干个字母 后面进行填充 但第一个位置填充的必须比原字符串的字符大才满足字典序比原串大(而也有可能等于原字符串 所以只要一开始特判一下原串是否满足要求即可)
先用mp预处理原字符串每个字母的个数 用于后续操作 从后往前枚举待填字符时就实时mp[i]--
对于要填充的位置我们记录个数 然后再根据已经固定的字符个数判断还要那些字符才满足是k的倍数 记录总个数 如果总个数比待填充的个数大说明该方案不行 直到找到一个方案 注意可能代填个数比总个数大 那么多余的位置就要填‘a' 这样保证最小
然后进行填充字符串 先填多余的a再遍历a-z每次判断mp[ch]是否能整除k 然后根据情况一次加就好了

#include<bits/stdc++.h>
#include<unordered_map>
#define ll long long
#define ull unsigned long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;

const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-4;
const ll N = 1e6 + 5;
const int M = 1e5 + 5;
const int mod = 1e9 + 7;
ll n, m;
string s;
map<char, ll>mp;
//判断是否满足要求
ll check(ll x) {
	ll cntt = 0;
	for (int i = 'a'; i <= 'z'; i++) {
		if (mp[i] % m) cntt += m - (mp[i] % m);
	}
	if (cntt <= x ) return x - cntt;//满足返回多余要加的'a'的数量
	return -1;
}

void solve() {
	cin >> n >> m;
	cin >> s;
	mp.clear();
        //预处理
	for (int i = 0; i < s.size(); i++) {
		mp[s[i]]++;
	}
        //特判断
	if (n % m) {
		cout << -1 << "\n";
		return;
	}
        //特判
	if (check(0) != -1) {
		cout << s << "\n";
		return;
	}
        //从后往前枚举开始填充的位置
	for (int i = s.size() - 1; i >= 0; i--) {
		mp[s[i]]--;
		ll cnt = s.size() - i - 1;
                //从原串后一个字符开始
		for (char j = s[i] + 1; j <= 'z'; j++) {
			mp[j]++;
			ll cc = check(cnt);//要加a的个数
			if (cc != -1) {
				s[i] = j;
               	ll k = i + 1;
				for (int i = 1; i <= cc; i++) {
					s[k] = 'a';
					k++;
				}
				for (char kk = 'a'; kk <= 'z' && k < s.size(); kk++) {
					while (mp[kk] % m) {
						s[k] = kk;
						mp[kk]++;
						k++;
					}
				}
				cout << s << "\n";
				return;//符合了直接return就好了
			}
			mp[j]--;//不符合减回去再去判断下一个
		}

	}
	
}

signed main()
{
	IOS;
	int t = 1;
	cin >> t;
	while (t--)
	{
		solve();
	}

}

D. Merge Sort

https://codeforces.com/contest/873/problem/D

题意

一个1-n的序列要先判断是否是升序 如果不是就把它分为两半[l, mid) 和[mid, r]再去判断这两段是否升序若不是就在子段中重复操作直到判断的区段是升序的 然后给定序列元素个数n 和判断次数k 让你构造一个1-n的序列满足恰好要判断k次

思路

可以计算出 对于n的序列至少判断一次至多判断2n-1次且不可能是偶数次
这个过程可以类比成树 可以先特判当前区段是否满足 即k-1后是否满足 满足就填充答案数组
然后每次特判-2后是否满足要求 满足就填充答案数组但是先填后半段 保证大段不升序 实时更新k
不满足就去和右半段比 比较2len-1与k+1因为之前-2中k减去了整段判断 如果k+1>=2len-1那就去左边半段重复操作右半段给予逆序
否者左半段给予升序 去右半段重复操作 实时更新k
填充答案数组时可以使用双指针右边的从xr开始 左边从xl开始 注意是否要逆序

#include<bits/stdc++.h>
#include<unordered_map>
#define ll long long
#define ull unsigned long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;

const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-4;
const ll N = 1e6 + 5;
const int M = 1e5 + 5;
const int mod = 1e9 + 7;
ll n, k, a[N];

bool comp(ll x, ll y) {
	return x > y;
}
ll xl = 1, xr = n;
void ss(ll l, ll r) {
	k -= 2;
    ll mid = (l + r - 1) / 2;
	if (k == 0) {//两端都是升序 但是整段不升序 那么就先填后面的
		for (int i = mid + 1; i <= r; i++) {
			a[i] = xl++;
		}
		for (int i = 1; i <= mid; i++) {
			a[i] = xl++;
		}
		return;
	}
	//右边判断的次数很多往右找 更新左边 左边升序但要大于右半段
	if ((r - mid) * 2 - 1 >= k + 1) {
		for (int i = mid; i >= l; i--) {
			a[i] = xr--;
		}
		ss(mid + 1, r);
	}//右边判断的次数很少往左找 更新右边 右边逆序但要大于右半段
	else {
		for (int i = r; i >= mid + 1; i--) {
			a[i] = xl++;
		}
		k -= 2 * (r - mid) - 2;
		ss(l, mid);
	}
}

void solve() {
	cin >> n >> k;
	if (k < 1 || k > 2 * n - 1|| k % 2 == 0) {
		cout << -1 << "\n";
		return;
	}
	k--;
	if (k == 0) {
		for (int i = 1; i <= n; i++) {
			cout << i << " \n"[i == n];
		}
		return;
	}
	ss(1, n);
	for (int i = 1; i <= n; i++) {
		cout << a[i] << " \n"[i == n];
	}
}

signed main()
{
	IOS;
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}

}
posted @ 2022-05-13 15:29  Yaqu  阅读(20)  评论(0)    收藏  举报