(2020牛客暑期多校训练营)[四] Dividing Strings

题目描述
长为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
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;
}

你可能感兴趣的:((2020牛客暑期多校训练营)[四] Dividing Strings)