P11100 [ROI 2022 Day 2] 交换
最简题意
给定正整数 \(n\) 和 \(k\),其中 \(n\) 可能含有前导零。你可以交换 \(n\) 中两个相邻的数码任意次,假设交换了 \(x\) 次,且最终得到的数为 \(n_1\),你需要最大化 \(n_1−xk\),并在此基础上最大化 \(n_1\)。
题意分析
虽然看似这个 \(n\) 很大,但是他就是很大,所以我们可以转战到 \(k\) 上面。这太小了,以至于仅仅只有十六位。不难发现,只要我们安排大于 \(k\) 之外的数,是不被 \(k\) 影响的,而且他只是求最终答案,与次数没关系。所以经粗略估计,我们可以将 \(n\) 分为两段。前一段直接用排序,因为它的取值与 \(k\) 无关。而后一段直接用贪心枚举次数,只要能换就换。而后一段大约在二十左右,但本篇题解用了二十六。具体可以参考代码及注释
更严谨的证明
当一次交换获得的收益超过 \(k\) 时我们一定会交换。一个数从个位交换到第 \(m\) 位至少需要交换 \(m-1\) 次,因此第 \(m\) 位从前 \(m-1\) 位找一个最大的数,至多需要代价 \((x-1)k\),而替换一个更大的数,权值至少增加 \(10^{x-1}\),注意到当 \(x\geq20\) 时,\(10^{x-1}\geq(x-1)k\) 成立。
CODE
#include<bits/stdc++.h>
#define wk(x) write(x),putchar(' ')
#define wh(x) write(x),putchar('\n')
#define int __int128
#define ull unsigned long long
#define ri register int
#define INF 2147483647
#define mod 998244353
#define N 200005
#define NO printf("No\n")
#define YES printf("Yes\n")
using namespace std;
int n,m,k,jk,ans,sum,num,cnt,tot;char a[N],b[N];
int head[N],dis[N],wis[N],f[N],A[N],B[N],kis[N];
void read(int &x)
{
x=0;int ff=1;char ty;
ty=getchar();
while(!(ty>='0'&&ty<='9'))
{
if(ty=='-') ff=-1;
ty=getchar();
}
while(ty>='0'&&ty<='9')
x=(x<<3)+(x<<1)+ty-'0',ty=getchar();
x*=ff;return;
}
void write(int x){
if(x==0){
putchar('0');
return;
}
if(x<0){
x=-x;
putchar('-');
}
char asd[201];int ip=0;
while(x) asd[++ip]=x%10+'0',x/=10;
for(int i=ip;i>=1;i--) putchar(asd[i]);
return;
}
struct P{
int x,id;
}vis[N];
bool cmp(P a,P b){
return a.x>b.x||(a.x==b.x&&a.id<b.id);
}
bool cmp1(P a,P b){
return a.id<b.id;
}
signed main()
{
scanf("%s",a+1);read(m);n=strlen(a+1);int pl=max(n-26,(int)1);
for(int i=1;i<=n;i++) dis[i]=a[i]-'0',vis[i].x=dis[i],vis[i].id=i;//输入。
if(n>26){//多余26位才考虑两段。
sort(1+vis,1+vis+n,cmp);//先全部排一遍序。
sort(1+n-26+vis,1+vis+n,cmp1);//后半段按编号排回去,用贪心处理。
}//记录
for(int i=1;i<=n;i++) dis[i]=vis[i].x;
for(int i=1;i<=n;i++) head[i]=dis[i];
for(int G=0;G<=26*26+100;G++)//枚举后一段需要修改多少次。
{
int G1=G;
__int128 p=0;//要开大一点。
for(int i=pl;i<=n;i++) dis[i]=head[i];//初始化。
for(int i=pl;i<=n;i++)
{
cnt=0;//一样初始化。
for(int j=i;j<=n;j++)
if(dis[j]>dis[cnt]&&G1>=j-i) cnt=j;//后面的比前面的大并且剩下的次数还够就记录。
if(cnt) G1-=(cnt-i);//如果存在就更新。
for(int j=cnt;j>i;j--) swap(dis[j],dis[j-1]);//更新答案。
}
for(int i=pl;i<=n;i++) p=p*10+dis[i];
if((p-G*m>ans)||(p-G*m==ans&&p>k)){//判断是否满足条件。
k=p;ans=p-G*m;//更新答案。
for(int i=pl;i<=n;i++) kis[i]=dis[i];//同理。
}
}
for(int i=1;i<pl;i++) write(dis[i]);//前面因为无论怎么换都有贡献,所以与答案没太大关联,另外输出。
for(int i=pl;i<=n;i++) write(kis[i]);//后面的部分。
return 0;
}
本文来自博客园,作者:Red_river_hzh,转载请注明原文链接:https://www.cnblogs.com/Red-river-hzh/p/19257061

浙公网安备 33010602011771号