题意:
链接:HDU - 6644
给你一个长度为 n 的数字,但是把其中的某些位使用 "?" 遮住了,然后让你把 "?" 遮住的部分填上数字,使得填填上后的数字 n ,满足 n % m == 0 。 有 q 次询问,每次询问要你输出满足条件的第 k 小的 n。
n,m,q(1≤n≤50000,2≤m≤20,1≤q≤100000) T(1≤T≤10000) k(1≤k≤10^18)
It is guaranteed that ∑n≤500000 and ∑q≤10^6 。
解题思路:
这里利用了一个比较好的思维方式,转化一下求解对象,比如给你一个 n 是:32???5? ,你可以把他看成 3200050 + ???0? ,然后你求出 3200050 % m = a。 那么其实你就只需要让 ???0? (? 代表 0 - 9 的数字) 满足 ???0? % m = m - a 。然后构造一个dp数组来描述状态,可以令 dp[ i ][ j ] 代表从右边起,填到第 i 个 ?时取模 m 为 j 的方案数。求完dp数组之后,根据他要求的 k ,从高位到低位枚举每一位,按照康拓展开的思路去求解答案,不过询问次数太多,如果每次都要把所有的 ?位都枚举了会T掉,不过呢其实用不到枚举那么多位置,因为 k 最大才 10 ^ 18 而且 m 很小,才 20。所以如果对于 ? 的个数超过30的,你其实只需要枚举低位的30个?就足够了,因为30个?就可以形成 10 ^ 30 种值,应该会把答案都覆盖进去,第30个 ?以后的都填 0 就可以。具体细节可以参照代码理解。
AC代码:
#include<bits/stdc++.h>
#define up(i, x, y) for(ll i = x; i <= y; i++)
#define down(i, x, y) for(ll i = x; i >= y; i--)
#define bug prllf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll maxn = 1e5 + 7;
const double pi = acos(-1);
const ll INF = 0x3f3f3f3f3f3f3f3f;
using namespace std;
ll n, m, q, k, len, cnt, tmm, tmod, len_0;
ll pos[maxn], pm[maxn], pmod[maxn];
ll dp[maxn][25];
char str[maxn];
ll power(ll a, ll b, ll mod)
{
ll ans = 1;
while(b)
{
if(b & 1)
ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
int main()
{
ll T; scanf("%lld", &T); while(T--)
{
tmm = 0, tmod = 0;
len_0 = 0;
scanf("%lld %lld %lld", &n, &m, &q);
scanf("%s", str + 1);
len = strlen(str + 1);
for(ll i = 1; i <= len; i++)
{
tmm = tmm * 10 % m; // 预处理出模数,以后直接在该基础上运算
tmod = tmod * 10 % mod; // 预处理出模数,以后直接在该基础上运算
if(str[i] != '?')
{
tmm += str[i] - '0';
tmod += str[i] - '0';
}
else
{
pos[++len_0] = len - i;
pm[len_0] = power(10, len - i, m); // 打表
pmod[len_0] = power(10, len - i, mod);
}
}
tmm %= m;
tmod %= mod; /****/
reverse(pos + 1, pos + 1 + len_0);
reverse(pm + 1, pm + 1 + len_0);
reverse(pmod + 1, pmod + 1 + len_0);
up(i, 1, len_0) memset(dp[i], 0, sizeof(dp[i]));
dp[0][0] = 1;
for(ll i = 1; i <= len_0; i++)
{
for(ll j = 0; j <= 9; j++)
{
int tmp = j * pm[i] % m; //ll tmp = tmm; tmp = (tmp + pm[i] * j % m) % m; 因为就在这一位,所以不用累计
for(ll k = 0; k < m; k++)
{
dp[i][ (tmp + k) % m ] += dp[i - 1][k]; // dp 递推
if(dp[i][ (tmp + k) % m ] > INF) // 过大,取个顶
dp[i][ (tmp + k) % m ] = INF;
}
}
}
tmm = (m - tmm + m) % m; // 剩余的需要提供的余数
ll tmp = tmod; // 当前 ? 都为 0 时候的答案。
while(q--)
{
scanf("%lld", &k);
if(dp[len_0][tmm] < k) /*****/
{
puts("-1");
continue;
}
ll ans = tmp; // 取最基础的答案,就是 ? 都是0
ll now_mod = tmm, nxt_mod;
for(ll i = min(len_0, 30LL); i >= 1; i--) // 康拓展开的思想
{
for(ll j = 0; j <= 9; j++) // 一直累计,这一位满
{
nxt_mod = (now_mod - pm[i] * j % m + m) % m; // 有下一位提供的余数的贡献值
if( dp[i - 1][nxt_mod] < k ) // 有多少个
{
k -= dp[i - 1][nxt_mod];
}
else // 个数太多,到下一位去
{
ans = (j * pmod[i] + ans) % mod;
now_mod = nxt_mod; /**** 下次需要替换 */
break;
}
}
}
printf("%lld\n", ans);
}
}
}