题目描述
长为n的十进制字符串s,将其分割为至少2个非空连续子串,并使得这些子串的最大值与最小值之差尽可能小。子串的值是其转化为十进制的数值。
例如,s=“1230”,可以将其分为"12"和"30",两个子串的值分别为12和30。注意,子串不能有前导0,例如对于字符串"001",只能将其分割为"0",“0"和"1”。
请输出字符串s分割后的子串最大值与最小值的差,这个差应该尽可能小
样例
输入
4
2
08
5
10199
7
9710296
8
12341234
输出
8
2
6
0
思路
我们先讨论一下差最大是几:脑袋一拍我们会知道,把每一位都分开来,使得每一个数都是个位数,则它们的差最大为9。因此,但凡算出来的差大于等于9,就直接舍去。
接下来,我们分两种情况讨论:
若把这个数字串分成长度等的几个数字,则需要枚举n的质因数。然后只需要一个一个进行求差,若有一个差是大于等于9的,则这个整体的差也肯定大于等于9的,直接舍去就行,再枚举下一个质因数。
若把这个数字分成长度不相等的两个数字。脑袋一拍我们会知道,这两个数字的数位差肯定是1,否则它们的差就比9要大了。而且这两个数还满足以下的形式:
一个数:100……x,中间有若干个0,可能有0个0,最后一位肯定是0~7之间
另一个数:99……y,中间有若干个9,可能有0个9,最后一位肯定是2~9之间
所以,只要寻找1,就能找到这一整个数了,也能找到另一个数。
代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
char s[MAXN];
int a[MAXN],b[MAXN],pr[MAXN],t,n,ans=9;
void DSJ(int* a,int *b,int n)//大数减
{
for(int i=n;i;i--)a[i]-=b[i];
for(int i=n;i;i--)if(a[i]<0)a[i-1]--,a[i]+=10;
}
bool cmp(int* a,int* b,int n)//比较两个大数的大小
{
for(int i=1;i<=n;i++)if(a[i]!=b[i])return a[i]<b[i];
return 0;
}
int solve_f(int len,int n)//所有数字位数相等的情况
{
if(len>1&&!a[1])return 9;//如果有前导0,直接返回9
int* mi=a,*mx=a;
for(int i=0;i<n;i+=len)
{
if(len>1&&!a[i+1])return 9;//如果有前导0,直接返回9
if(cmp(a+i,mi,len))mi=a+i;//更新最小值
if(cmp(mx,a+i,len))mx=a+i;//更新最大值
}
memcpy(b,mx,sizeof(int)*(len+10));DSJ(b,mi,len);//拷贝并进行大数减
for(int i=1;i<len;i++)if(b[i])return 9;
//如果除了个位数,其他数位上也有大于0的数,则差必然大于10,返回9
return b[len];
}
int solve_d(int len,int n)//数字位数相差1的情况
{
int p=1,zero=0,nine=9;
while(p<=n)
if(a[p]==1)//若找到了一个数,1肯定是在0的前面
{
if(p+len>n||pr[p+len-1]-pr[p])return 9;
//如果超出总的位数或后一位不是0,返回9
zero=max(zero,a[p+len]);p+=len+1;//记录1000……的最后一位数
}
else//否则就是另一个数
{
if(p+len-1>n||pr[p+len-2]-pr[p-1]!=9*(len-1))return 9;
//如果超出总的位数或后面不是有(len-1)个9,返回9
nine=min(nine,a[p+len-1]);p+=len;//记录999……的最后一位
}
return 10-nine+zero;//返回求差后的值
}
int main()
{
for(scanf("%d",&t);t--;ans=9)
{
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;i++)a[i]=s[i]-'0',pr[i]=pr[i-1]+a[i];
//求前缀和,可以快速算出一个区间内所有数字的和
for(int i=1;i<=n/2;i++)//求出所有情况的最小值
{
ans=min(ans,solve_d(i,n));
if(n%i==0) ans=min(ans,solve_f(i,n));
}
printf("%d\n",ans);
}
return 0;
}