sgu 108 Self-numbers 2

来源:互联网 发布:软件sp版本是什么意思 编辑:程序博客网 时间:2024/05/16 15:45

        一些特殊的自然数可以表示成另一个自然数+这个数每一位的数字之和。现在要求出1--N中,有多少个数不是这样的数,并且给出K个下标,求1---N中从小到大第Ki个不能这样表示的数是多少...比如 8=4+4,15=12+1+2....这些都是可以表示的,但3,5,7...这样的数是无法表示出来的。。。

          这题N虽然给了1e7的大小,但O(n)跑一遍是不会超时的,恩,,这题不卡时间,卡的是空间- -...空间给了4096KB,把1e7以内符合要求的数存下来是不可能的-但是可以利用1个64为整数来标记着64个数是否符合要求(每一位标记0,1即可)。。然后用cnt【i】存一下前i个64位整数里有多少个符合要求的数,第一问就直接出来了。。。对第二问,每个下标做一次二分查找,找到第Ki大的在哪个64位整数的第几位上就ok。

          

#include<iostream>#include<cstdio>#include<memory.h>#include<algorithm>#include<math.h>using namespace std;const int inf=0x3f3f3f3f;typedef unsigned long long ll;const int MOD=1e9;const int maxn=1e7;ll vis[160000+2000];int cnt[160000+2000];ll count(ll n){    ll c =0 ;    for (c =0; n; ++c)    {        n &= (n -1) ;     }    return c ;}ll n;int num;int m;int k,p;int slove(int num){  int low=0,high=1e7/63;  int mid;  while(low<high)  {      mid=(low+high)>>1;      if (cnt[mid]<num) low=mid+1;      else high=mid;  }  int p=num;  if (low>0) p-=cnt[low-1];  if (p==0) return 63*low;  for (int i=0; i<63; i++)  {      if ((vis[low] & 1LL<<(i))==0) p--;      if (p==0) return 63*low+i;  }}int main(){//    freopen("in.txt","r",stdin);//    freopen("out.txt","w",stdout);    memset(vis,0,sizeof vis);    memset(cnt,0,sizeof cnt);    num=0;    for(ll i=1;i<=1e7;i++)    {        ll tmp=i;        int p,q;        int j=i;        while(j)        {            tmp+=j%10;            j/=10;        }        p=tmp/63;        q=tmp%63;        vis[p]|=(1LL<<(q));    }    vis[0]|=1;    num=1e7/63;    for (int i=0; i<=num; i++)    cnt[i]=count(vis[i]);    for (int i=0; i<=num; i++)    cnt[i]=63-cnt[i];    for (int i=1; i<=num; i++)    cnt[i]+=cnt[i-1];    scanf("%d%d",&m,&k);    int low=m/63;    int ans=0;    if (low>0) ans+=cnt[low-1];    m%=63;    for (int i=0; i<=m; i++)    if ((vis[low]& (1LL<<(i)))==0) ans++;    cout<<ans<<endl;    for (int i=1; i<k; i++)    {        scanf("%d",&p);        cout<<slove(p)<<" ";    }    scanf("%d",&p);    cout<<slove(p)<<endl;    return 0;}