【题解】HDU6659 Acesrc and Good Numbers 搜索剪枝,数位

f ( d , n ) f(d,n) f(d,n)表示 1 1 1 n n n中出现的 d ∈ ( 1 , 9 ) d\in(1,9) d(1,9)的数量, 1500 1500 1500组询问,找到不超过 x x x的最大数 k k k,使得 f ( d , k ) = k f(d,k)=k f(d,k)=k


首先打表观察性质:

  1. 满足条件的数不会超过1e11
  2. 满足条件的数出现非常稀疏

实际上问题是要找 f − i f-i fi的零点,把数分成高6位和低5位:

  1. 如果高6位出现了这个数字,那么 f − i f-i fi单调不减,如果当前高6位到下一个高6位的两个端点处 f − i f-i fi符号不同,证明其中有零点,枚举低5位。
  2. 如果高6位没有出现这个数字,那么只有当 f − i < 1 e 5 f-i<1e5 fi<1e5时才可能有零点。

通过这些优化,可以很快地打出所有满足条件的数字的表,然后每次查询直接在表中查找即可。


细节:

  1. 1 e 6 1e6 1e6的数字出现次数表时,可以使用save[i]=save[i/10]+(i%10==d)的递推方式。
/* LittleFall : Hello! */
#include 
using namespace std; using ll = long long; inline ll read();

const ll E5 = 100000, E6 = 1000000;

ll save[E6];
bool check(ll a, ll b)
{
	if(a<0 && b>=0) return 1;
	if(a>=0 && a>b && a<E5) return 1;
	return 0;
}
int main(void)
{
	#ifdef _LITTLEFALL_
	freopen("in.txt","r",stdin);
    #endif

	vector<ll> table[10];
	for(int d=1; d<=9; ++d)
	{
		table[d].push_back(0);
		for(int i=1; i<E6; ++i)
			save[i] = save[i/10] + (i%10==d);

		ll r = 1;
		for(ll i=0; i<E6; ++i) //段
		{
			if(check(r, r + save[i]*E5 - E5/2))
			{
				for(ll j=0; j<E5; ++j)
				{
					r += save[i] + save[j] - 1;
					if(r==0)
						table[d].push_back(i*E5+j);
				}
			}
			else
				r = r + save[i]*E5 - E5/2;
		}
	}

	int t = read();
	while(t--)
	{
		ll b = read(), x = read(); //最后一个小于等于x的数
		ll res = *(upper_bound(table[b].begin(), table[b].end(), x)-1);
		cout << res << endl;
	}

    return 0;
}


inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

你可能感兴趣的:(题解)