文章目录
- 题目
- 问题描述
- 分析
-
- 思路一(贪心,枚举)
-
- 具体操作
- 代码
- 思路二(DFS)
-
- 具体操作
- 代码
- 思路三(map+贪心)
-
- 具体操作
- 代码
题目
Nearest Beautiful Number (hard version)
问题描述
与简单版的区别在于k的范围由原来的 k < = 2 k<=2 k<=2拓展到了 1 < = k < = 10 1<=k<=10 1<=k<=10
由于数据范围的变化,之前的思路就不适用于这里了。
分析
思路一(贪心,枚举)
通过set的互异性,来记录x中的数字种数,一旦x中的数字种数小于等于k,就结束操作,返回结果。
先令x等于n,要注意的是,在操作中,我们保证每次操作的结果都会大于原先的x。
具体操作
从第一位开始,我们保证在前缀里面出现的数字种数小于等于k,如果当前这一位放入集合后不会使得总数大于k,就继续处理下一位。
while (true){
set<char> s;for (auto c : n) s.insert(c);if (s.size() <= k) return n;s.clear();int ptr = 0;for (; ; ptr++){
s.insert(n[ptr]);if (s.size() > k){
//......break;}}}
若当前位进入集合后造成种数大于k,那么就对前缀(包括当前位)进行加一操作,对后续置零。
比如当n=1292,k=2时,处理到第三位’9’的时候,结果大于k,就对前缀加一,后续置零,得到1300;
再处理1300,仍然再处理第三位时出现大于k的情况继续加一置零,得到1310(有点枚举的味道了);
再继续处理1310,处理到第四位时,加一,后续已无法置零,得到1311,满足条件,这就是结果了。
加一:是枚举可能,并且扩大x;
置零:保证扩大后的结果是前缀满足条件的情况下最小的。
这样操作的话,时间复杂度主要跟长度有关,若长度为m,则为 O ( m 2 ) O(m^2) O(m2)
代码
#include <bits/stdc++.h>using namespace std;string solve()
{
string n;int k;cin >> n >> k;while (true){
set<char> s;for (auto c : n) s.insert(c);if (s.size() <= k) return n;s.clear();int ptr = 0;for (; ; ptr++){
s.insert(n[ptr]);if (s.size() > k){
while (n[ptr] == '9')ptr--;n[ptr]++;for (int i = ptr + 1; i < n.size(); i++)n[i] = '0';break;}}}
}int main()
{
int t;cin >> t;while (t--)cout << solve() <<endl;return 0;
}
思路二(DFS)
具体操作
区别于上面记录种数的方法,这里记录数字种数是通过二进制上面1的个数来记录.
设参数为mask,当我们向mask里面放入数字5时,可以通过mask|(1<<5) 放入5,若存在,由于或运算不会影响;若不存在,就置该位为1;
同时有一个函数__ b u i l t i n builtin builtin_ p o p c o u n t ( ) popcount() popcount()可以直接求出二进制中1的个数,很方便。
解释一下代码中一些参数的作用:
全局变量ans:标记有无结果产生,使用前要先置零。
dfs参数lim:标记是否可以沿用原数据的值作为当前循环的起点,初始为1,过程中,在调整的时候置零,表示要从0开始依次搜索。
dfs参数pos:搜索所处的位数,初始为1,当pos为len+1时,说明前len个已经有了答案。此时可以返回结果,结束循环。
dfs参数mask:以二进制来记录当前已经加入的数字种数。
dfs参数sum:记录结果,也就是前缀的值。
仍然以n=1292,k=2为例,把过程走一遍,
处理第一位1时,mask变为 ( 10 ) 2 (10)_2 (10)2?,计数的值小于等于2,进入下一层;
处理第二位2时,mask变为 ( 110 ) 2 (110)_2 (110)2?,计数结果仍然小于等于2,进入下一层;
处理第三位9时,mask变为 ( 1000000110 ) 2 (1000000110)_2 (1000000110)2?,计数结果为三,大于2,返回上一层;
此时处于第二位,要对2的下一位3进行搜索,此时mask变为 ( 1010 ) 2 (1010)_2 (1010)2?,计数结果小于等于2,同时lim要置为0,表示对下一层从0开始搜索;
第三层,0会使种数大于2,测试到1时,mask为 ( 1010 ) 2 (1010)_2 (1010)2?,满足条件,下一层,仍然以lim为0开始搜索,所以这个的时间复杂度不会太理想。
最后得到结果1311.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
char s[12];
int len,ans,k;
int cal(int mask)//计算二进制有多少个1
{
return __builtin_popcount(mask);
}
void dfs(int lim,int pos,int mask,int sum)
{
if(pos==len+1){
ans=sum;return ;}int start=0;if(lim){
start=s[pos];}for(int i=start;i<=9;i++){
if(ans==0&&cal(mask|(1<<i))<=k){
dfs(lim&&i==start,pos+1,mask|(1<<i),sum*10+i);}}
}
int main()
{
int t;int n;cin>>t;while(t--){
scanf("%s",s+1);cin>>k;len=strlen(s+1);for(int i=1;i<=len;i++){
s[i]-='0';}ans=0;dfs(1,1,0,0);cout<<ans<<endl;}return 0;
}
思路三(map+贪心)
具体操作
再次区别上方记录种数的方法,这里还可以使用map键值对来记录,而且map会进行自动排序,方便后续从中进行选择。
map<char, int> d;int pref = 0;while (pref < n.length())//计算原来的数字种数 {
if (d.count(n[pref])){
d[n[pref++]]++;continue;}if (d.size() == k) break;d[n[pref++]]++;}
依旧以n=1292,k=2为例,把过程走一遍,
先进行一次循环,计算数字种数的同时,若满足条件会返回结果,但结束循环时并没有满足条件,还需要往下进行,此时可以得知是在第二位使得跳出循环,且此时pref指向了第三位,map刚好有两组键值对。
第三位恰为9,所以需要处理这一位的上一位,将其对map的影响从map中抹除,可能是把值减一,也可能是删除那组键值对,此时对应的是删除。
保证了pref所对应的位数不是9后,若经过删除使得键值对恰好为k-1(删除恰好改变了键值对数目,而且之前为k,此时若改变只会为k-1),此时pref所处位进行加一操作并再次加入map中,再从map中选出最小值(不会影响k)对后续进行填充,若仍然满足k-1,则可以取更小的0来填充。
从而此时就已经得到了结果1311。
当然了,若采用另外的数据,删除之后map依旧满足等于k,则需要从map找出第一个大于当前值的键值对,若找到,用其来填充后续,满足最小的条件;
若找不到,删除上一位的影响,继续循环。
代码
#include <bits/stdc++.h>using namespace std;string solveFillingSuffix(string& n, char d, int from)//从from开始,将n中后续都置为d
{
for (int i = from; i < n.length(); i++)n[i] = d;return n;
}void decAt(map<char, int>& d, char c)//抹除c对map的影响
{
if (d.count(c)){
d[c]--;if (d[c] == 0) d.erase(c);}
}string solve()
{
string n;int k;cin >> n >> k;map<char, int> d;int pref = 0;while (pref < n.length())//计算原来的数字种数 {
if (d.count(n[pref])){
d[n[pref++]]++;continue;}if (d.size() == k) break;d[n[pref++]]++;}if (pref == n.length())//成功遍历,没有触发break,种数小于等于k return n;while (true){
if (n[pref] == '9'){
decAt(d, n[--pref]);continue;}if (d.size() < k)//返回结果 {
d[++n[pref]]++;return solveFillingSuffix(n, d.size() < k ? '0' : d.begin()->first, pref + 1);}auto it = d.upper_bound(n[pref]);//从map里面找出第一个大于当前位的值 if (it == d.end())//如果没找到 {
decAt(d, n[--pref]);continue;}n[pref] = it->first;return solveFillingSuffix(n, d.begin()->first, pref + 1);//返回结果 }
}int main()
{
int t;cin >> t;while (t--)cout << solve() << '\n';return 0;
}