sgu 108 Self-numbers 2

38 篇文章 0 订阅

        一些特殊的自然数可以表示成另一个自然数+这个数每一位的数字之和。现在要求出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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值