zoukankan      html  css  js  c++  java
  • 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
    ",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
  • 相关阅读:
    设置发光字
    QQ空间无导航条应对方法
    网页设计经典网站欣赏
    页面居中显示
    获取元素的绝对位置
    输入两个整数 n 和 m,从数列1,2,3.......n 中 随意取几个数, 使其和等于 m ,要求将其中所有的可能组合列出来.
    最长重复子字符串
    从头到尾彻底解析Hash 表算法
    求二叉树中节点的最大距离
    MySQL学习笔记——显示数据库信息
  • 原文地址:https://www.cnblogs.com/Narh/p/10883293.html
Copyright © 2011-2022 走看看