2020牛客暑期多校训练营Dividing Strings(贪心,分类讨论)

Dividing Strings

题目描述

2020牛客暑期多校训练营Dividing Strings(贪心,分类讨论)_第1张图片

输入描述:

2020牛客暑期多校训练营Dividing Strings(贪心,分类讨论)_第2张图片

输出描述:

在这里插入图片描述

示例1

输入

4
2
08
5
10199
7
9710296
8
12341234

输出

8
2
6
0

题目大意

给定长度为n的数字,你可以将其分割成几个更小的数字。求对于任意一种分割方式, m a x − m i n max-min maxmin的最小值是多少。

分析

深解题意:例如给定12341234,你可以分成123|412|34,此时 m a x − m i n max-min maxmin=412-34,显然不是最小。而真正的最小是分成1234|1234,此时 m a x − m i n max-min maxmin=1234-1234=0,最小。

定理

稍一分析,可知答案必然小于10。因为不管怎样,总是可以将其每个数字都分开,此时答案最大是9-0=9,因此任何解一旦超过9,就一定不如每个数字全部分开,即非最优。

推理

由此可以推得,最大值和最小值要么都是同样的位数,要么就是1000…x,9999…y的样子,这样才能保证它们的差不比沙雕解法差。

分类

推理后即可得出正解,分两种情况:

第一种情况,同样的位数

此时,位数必定是n的因子,因此只要枚举n的因子,然后算一遍答案,与历史最优取优即可。

第二种情况,位数差1

此时,其形式是固定的,对于每个长度,都去找一遍这个最后一位的x,y即可。

本题恶心之处

高精,下标,指针。容易成为做题时的WAWA点。

代码

#include
#define ll long long
#define inf 1<<30
using namespace std;
void sub(int* a,int* b,int len){
	for(int i=1;i<=len;i++) a[i]-=b[i];
	for(int i=len;i>=1;i--) if(a[i]<0) a[i]+=10,a[i-1]--;
}//同位高精减法
bool cmp(int* a,int* b,int len){for(int i=1;i<=len;i++) if(a[i]!=b[i]) return a[i]<b[i];return 0;}
//比较大小,小返回1
const int MAXN=1e5+10;
char s[MAXN];
int a[MAXN],pre[MAXN],b[MAXN];//pre前缀和,用于快速判断差1时的两种形式
int sol_xd(int p,int len){//第一种情况
	if(p>1&&!a[1]) return 9;//前导0存在的话就直接不行了,位数不一样了
	int *mx=a,*mn=a;//啊指针,有大佬不用指针的改法吗,(用for的话也还是算了)发下。
	for(int i=0;i<len;i+=p){
		if(p>1&&!a[i+1]) return 9;//前导0
		if(cmp(a+i,mn,p)) mn=a+i;
		if(cmp(mx,a+i,p)) mx=a+i;//打擂台求最值
	}
	memcpy(b,mx,sizeof(int)*(p+10));//+10防止减成负数,cpy一份是因为指针会导致原来的数组也改变
	sub(b,mn,p);//减
	for(int i=1;i<p;i++) if(b[i]) return 9;//如果减出来不是一位数,那么也是不对的
	return b[p];
}
int sol_cha1(int p,int len){//第二种情况
	int pos=1,z_last=0,n_last=9;
	while(pos<=len){
		if(a[pos]==1){
			if(pos+p>len||pre[pos+p-1]-pre[pos]>0) return 9;//快速判断是否全0
			z_last=max(z_last,a[pos+p]);pos+=p+1;
		}
		else{
			if(pos+p-1>len||pre[pos+p-2]-pre[pos-1]!=p*9-9) return 9;//快速判断是否全9
			n_last=min(n_last,a[pos+p-1]);pos+=p;
		}//这里的最大和最小要想清楚
	}return 10-n_last+z_last;
}
int main()
{
	int t,n;
	scanf("%d",&t);
	while(t--){
		int ans=9;
		scanf("%d",&n);scanf("%s",s+1);
		for(int i=1;i<=n;i++) a[i]=s[i]-'0';
		for(int i=1;i<=n;i++) pre[i]=pre[i-1]+a[i];//求前缀和
		for(int i=1;i<=n/2;i++){
			ans=min(ans,sol_cha1(i,n));//第二种每个长度都求
			if(n%i==0) ans=min(ans,sol_xd(i,n));//第一种是因子才求
		}
		printf("%d\n",ans);
	}
}

END

好难一题,讨厌的全上了,高精+指针,woc
感谢。

你可能感兴趣的:(2020牛客多校)