APIO2019 练习赛 Wedding cake——思路+高精度
题目大意:
给 n ( n<=1e5 ) 个数 \( a_i \) (\( a_i \) <=1e5),需要构造 n 个实数使得它们的和是 1 ,并且第 i 个实数必须小数点后恰好有 \( a_i \) 个有意义的数位。有意义的数位指的是到最后一个非0位为止的数位。
Subtask 1 (17 pts) : n<=100 , \( a_i \) <=10
Subtask 2 (21 pts) : n<=1e5 , all \( a_i \) are equal
Subtask 3 (25 pts) : n<=1000 , \( \sum a_i \) <=1000
Subtask 4 (37 pts) : 没有特殊性质
想到一个构造方法。就是数位限制按从多到少排序, \( a_i \) 相同的一些实数,每个都是 0.000..001 ,只有最后一个用来补齐,使得至今为止的和是满足下一个较小的 \( a_i \) 的限制的。
如果补齐恰好使得该实数的数位少于 \( a_i \) ,那么只需要让 \( a_i \) 相同的某个实数从 0.000..001 变成 0.000..002 ,该实数就能减少 0.000..001 ,从而数位符合要求。
如果需要做上面那个操作,但 \( a_i \) 是该值的只有该实数一个,那么就无解。其实这里感觉有些不对,因为可以调更之前的一些数,但就这样写也能过。
可惜 double 无法做到 1e5 的精度。所以用 long long ,只有 17 分。
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define ll long long using namespace std; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return fx?ret:-ret; } int Mx(int a,int b){return a>b?a:b;} const int N=1e5+5; int n,a[N],mx,g[20]; ll ans[N],bin[15]; struct Node{ int a,id; bool operator< (const Node &b)const {return a>b.a;} }c[N]; bool chk(int x) { ll tp=ans[c[x].id]; int lm=c[x].a,cnt=0; while(tp&&tp%10==0)tp/=10,cnt++; return mx-cnt!=lm; } int main() { n=rdn(); for(int i=1;i<=n;i++) { a[i]=c[i].a=rdn(); c[i].id=i; mx=Mx(mx,a[i]); } bin[0]=1; for(int i=1;i<=mx;i++)bin[i]=bin[i-1]*10; sort(c+1,c+n+1); ll bs,bs2=bin[mx-c[1].a],lj=0; for(int i=1;i<=n;i++) { bs=bs2; int j=i; ans[c[i].id]=bs; lj+=bs; while(j<n&&c[j+1].a==c[j].a) j++, ans[c[j].id]=bs, lj+=bs; bs2=bin[mx-c[j+1].a]; ll tp=bs2; while(tp<lj)tp+=bs2; swap(tp,lj); tp=lj-tp; ans[c[i].id]+=tp; if(chk(i)) { if(j==i){puts("NO");return 0;} ans[c[i].id]-=bs; ans[c[i+1].id]+=bs; } i=j;// } if(lj>bin[mx]){puts("NO");return 0;} puts("YES"); for(int i=1;i<=n;i++) { printf("0."); while(ans[i]%10==0)ans[i]/=10; int t=0; while(ans[i])g[++t]=ans[i]%10,ans[i]/=10; for(int j=t+1;j<=a[i];j++)g[j]=0; for(int j=a[i];j;j--)putchar(g[j]+'0'); puts(""); } return 0; }
然后看了这个题解:https://blog.csdn.net/lycheng1215/article/details/80246134
用高精度实现就可以了。实现方法也是借鉴那个题解的……
大概就是用 vector 的 rem[ i ] 表示第 i 种 a 的用来补齐的那个实数的值。记录成一个大数,末位就是小数点后第 \( a_i \) 位。
先算出每个 a 对应了多少个实数,然后从大到小遍历 a ,记录一个 lj 表示之前的数已经带来的贡献。lj 是一个 int 类型的,个位表示小数点后第 \( a_i \) 位。
设 ct 表示当前的 a 对应了 ct 个实数,那么它们都填 1 ,再算上之前补上来的,和就是 tmp = lj+ct ;
枚举数位从 \( a_i \) 到 \( a_{i+1} \) (\( a_{i+1} < a_i \) ),tmp 一直 /10 ,做完之后的 tmp 就是当前的和进到下一个 a 是多少了;
同时记录一个 hs 表示 tmp /10 的过程中,有没有非0位;如果有的话,做完 /10 操作之后的 tmp 就需要再 +1 ,因为补足是要上取整的。
然后 rem[ i ] 的初值就是:先有 \( a_i - a_{i+1} \) 个 0 ,然后是一个数 tmp+hs 。
然后用 rem[ i ] 减去刚才的 lj ,再减去 ct-1 ,如果 (ct-1)%10 == rem[ 0 ] (这里的 rem[ 0 ] 是减去 lj 之后的,相等表示 ct-1 个实数都填 1 之后,补齐的那个实数会缺少一些数位),rem[ i ] 再减去 1 即可,同时给 i 打一个标记表示它有一个填 2 的实数。
然后 lj 就变成刚才的那个 tmp+hs 即可。
lj 是可以用 int 类型的,因为每次加 ct ,最多在加一个 hs ,还会不断 /10 ,不会超过 2e5 ; 之所以 rem[ ] 需要用高精度,是因为 \( a_i \) 到 \( a_{i+1} \) 可能有 1e5 个位置,用来补齐的那个实数就可能有这么多不平凡的数位。
#include<cstdio> #include<cstring> #include<algorithm> #include<vector> #define pb push_back using namespace std; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return fx?ret:-ret; } const int N=1e5+5; int n,mx,a[N],ct[N],pr[N]; bool vis[N]; vector<int> rem[N]; void print(int cr,int fx) { printf("0."); for(int i=1;i<a[cr];i++) putchar('0'); printf("%d\n",fx); } void printx(int cr) { int bh=a[cr],siz=rem[bh].size(); printf("0."); for(int i=a[cr]-siz;i;i--) putchar('0'); for(int i=siz-1;i>=0;i--)//-- not ++!!! printf("%d",rem[bh][i]); puts(""); } void add_frs(int cr) { int siz=rem[cr].size()-1; for(int i=0;i<siz;i++) if(rem[cr][i]>10) { rem[cr][i+1]+=rem[cr][i]/10; rem[cr][i]%=10; } while(rem[cr][siz]>10) { rem[cr].pb(rem[cr][siz]/10); rem[cr][siz]%=10; siz++; } } void dec_frs(int cr) { int siz=rem[cr].size()-1; for(int i=0;i<siz;i++) if(rem[cr][i]<0) { int tp=-rem[cr][i]; tp=tp/10+(tp%10?1:0); rem[cr][i]+=10*tp; rem[cr][i+1]-=tp; } while(!rem[cr][siz]) rem[cr].pop_back(), siz--; } void solve() { int lj=0; for(int i=mx;i;i=pr[i]) { int tmp=lj+ct[i]; bool hs=0; for(int j=i;j>pr[i]&&tmp;j--)//go to lst pos { hs|=tmp%10; tmp/=10;} rem[i].resize(i-pr[i]);//some 0 based a[i] rem[i].pb(tmp+hs); add_frs(i); int tp=lj; lj=tmp+hs; for(int j=0;tp;j++) { rem[i][j]-=tp%10; tp/=10;} dec_frs(i); tp=ct[i]-1; if(tp%10==rem[i][0]) { if(ct[i]==1){puts("NO");return;} tp++;vis[i]=1;//some is 2 } for(int j=0;tp;j++) { rem[i][j]-=tp%10; tp/=10;} dec_frs(i); } if(lj>1){puts("NO");return;} puts("YES"); for(int i=1;i<=n;i++) { ct[a[i]]--; if(vis[a[i]]) { vis[a[i]]=0; print(i,2);} else if(ct[a[i]]) { print(i,1);} else printx(i); } } int main() { n=rdn(); for(int i=1;i<=n;i++) { a[i]=rdn(); ct[a[i]]++;} for(int i=1e5;i;i--)if(ct[i]){mx=i;break;} for(int i=1,lst=0;i<=mx;i++) { pr[i]=lst; if(ct[i])lst=i;//if()!! } solve(); return 0; }