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;
}
View Code

  然后看了这个题解: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;
}
View Code

 

posted on 2019-05-17 19:37  Narh  阅读(264)  评论(0编辑  收藏  举报

导航