题意:
设$s(i)$为将$1\sim i$看做字符串后依次连接形成的串。给定正整数$n$,求最小的$i$使得$n$是$s(i)$的字串。$T$组数据。
$n\le 10^{17}, \; t\le 10^4$
分析:
不能模拟$s(i)$的组成过程来找答案,时间不能承受。
也不能预处理$s(k)$,空间不能承受。
那就只能在$n$上找答案。
以下把数字当成字面量来讨论,更方便。
同时,这里讨论的前缀和后缀不包括本身。
思考一下,答案分为三种:
1.$ans=n$
2.$n$由$ans$的前缀和$ans-1$的后缀组成
3.$n$由$ans-k$的后缀和$ans-k+1 , \, ans-k+2 \dots ans-2 , \, ans-1$整串和$ans$的前缀组成
那么把三类答案的所有可能取最小值即可。
先确定一下我们模拟的工具。用字符串还是数字?
字符串更方便控制位置,数字在比较、增减的时候更方便。
我这里用一种取两家之长的做法:
1.以五倍最大长度($N=17$)存储每一位数字
2.用头尾指针记录数字的位置,初始化数字首位在$2*N+1$上。非数字位数值为$-1$,比较时视作通配符。
3.定义左移、右移、找零、判九等函数辅助操作
个人感觉这个模型的灵活度很高,适用于数字$\times$字符串的操作。
现在看到第二类答案。
首先枚举“切一刀”的位置,设分离出的数字为$x$和$y$,将$y$从最左边开始向右卡位,尝试匹配。
边界一:$y$的右侧不能和$x$的右侧平齐。连续的自然数数值是有变化的,如果两个数字的右侧平齐,甚至$y$的右侧越过$x$的右侧,$x$必然不是$ans-1$的后缀。
边界二:$y$的左侧不能和$x$的左侧平齐。这和我们的定义相违背:$ans$相邻的数字不是$n$的子串。
注意,这里的“匹配”,有可能有进位。
看到第三类答案。
首先枚举基准数,再将它的相邻数字往左、右匹配。这里凸显出了数据模型的灵活性。
基准数不能等于原数。
数字减少时可能会出现$0$,注意应对。
注意数字位数的变化,左右移动的距离要根据当前数字的长度来。
实现(100分):
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define IL inline using namespace std; typedef long long LL; const int N=17; IL void swap(LL &x,LL &y){ LL t=x; x=y; y=t; } int T; LL n; struct Num{ LL s[N*5+1]; int p,q; IL LL operator[](int i){ return s[i]; } IL int len(){ return q-p+1; } IL bool err(){ if(p>q) return true; if(s[p]==0) return true; for(int i=p;i<=q;i++) if(s[i]==-1) return true; return false; } }org; IL Num fill(LL x){ Num a; memset(a.s,-1,sizeof a.s); int l=0; while(x>0){ a.s[++l]=x%10LL; x/=10LL; } for(int i=1;i<=l/2;i++) swap(a.s[i],a.s[l-i+1]); for(int i=l;i>=1;i--) a.s[2*N+i]=a.s[i]; for(int i=1;i<=2*N;i++) a.s[i]=-1; a.p=2*N+1; a.q=2*N+l; return a; } IL LL val(Num a){ LL ret=0; for(int i=a.p;i<=a.q;i++) ret=ret*10LL+a[i]; return ret; } IL Num copy(Num a,int l,int r){ for(int i=a.p;i<=a.q;i++) if(i<l||i>r) a.s[i]=-1; a.p=l; a.q=r; return a; } IL Num shl(Num a,int k){ int p1=a.p-k,q1=a.q-k; for(int i=a.p;i<=a.q;i++) a.s[i-k]=a[i]; for(int i=q1+1;i<=a.q;i++) a.s[i]=-1; a.p=p1; a.q=q1; return a; } IL Num shr(Num a,int k){ int p1=a.p+k,q1=a.q+k; for(int i=a.q;i>=a.p;i--) a.s[i+k]=a[i]; for(int i=a.p;i<p1;i++) a.s[i]=-1; a.p=p1; a.q=q1; return a; } IL bool eql(Num a,Num b,int l,int r){ for(int i=l;i<=r;i++) if(a[i]==-1||b[i]==-1) continue; else if(a[i]!=b[i]) return false; return true; } IL bool operator<(Num a,Num b){ return val(a)<val(b); } IL int f0(Num a){ int ret=a.q; while(a[ret]==0) ret--; return ret+1; } IL bool all9(Num a,int l,int r){ for(int i=l;i<=r;i++) if(a[i]!=9) return false; return true; } IL Num operator+(Num a,Num b){ for(int i=b.p;i<=b.q;i++) a.s[i]=b[i]; a.q=b.q; return a; } IL Num inc(Num a){ int pos=a.q; a.s[pos]++; while(a[pos]==10){ a.s[pos]=0; a.s[--pos]++; } if(pos<a.p) a.s[--a.p]=1; return a; } IL Num dec(Num a){ int pos=f0(a); a.s[pos-1]--; bool flag=false; if(a.p==pos-1&&a.s[pos-1]==0){ flag=true; a.p++; } for(int i=pos;i<=a.q;i++) a.s[i]=9; if(flag) a=shl(a,1); return a; } int main(){ freopen("number.in","r",stdin); freopen("number.out","w",stdout); scanf("%d",&T); while(T--){ scanf("%lld",&n); Num org,ans; org=ans=fill(n); int l=org.len(); for(int pos=org.q;pos>org.p;pos--){ if(org[pos]==0) continue; Num x=copy(org,org.p,pos-1); Num y=copy(org,pos,org.q); y=shl(y,l); while(y.q<x.q&&y.p<x.p){ int pos0=f0(y); if(eql(x,y,y.p,pos0-2) &&all9(x,pos0,x.q) &&(x[pos0-1]==-1 ||x[pos0-1]+1==y[pos0-1])){ Num z=copy(y,y.p,y.q); for(int i=y.q+1;i<=x.q;i++) z.s[i]=0; z.q=x.q; if(eql(y,z,y.p,y.q)) ans=min(ans,z); } if(eql(x,y,y.p,y.q)){ Num z=inc(y+copy(x,y.q+1,x.q)); if(eql(y,z,y.p,y.q)) ans=min(ans,z); } y=shr(y,1); } } for(int i=org.p;i<=org.q;i++) for(int j=i;j<=org.q;j++){ if(j-i+1>=l) continue; if(org[i]==0) continue; Num x=copy(org,i,j); bool flag=true; while(flag){ x=dec(x); x=shl(x,x.len()); if(x.q<org.p) break; if(x.err()||!eql(x,org,x.p,x.q)) flag=false; } x=copy(org,i,j); while(flag){ x=inc(x); x=shr(x,x.len()); if(x.p>org.q) break; if(!eql(x,org,x.p,x.q)) flag=false; } if(x.p>org.q) x=dec(x); if(flag) ans=min(ans,x); } printf("%lld\n",val(ans)); } return 0; }
小结:
应对神奇大模拟,优秀的模型往往会有事半功倍的效果。
原文地址:https://www.cnblogs.com/Hansue/p/11405092.html