比赛链接:传送门
题目大意:
求一个十进制大数S(有部分数位为"?")能被N整除时的最小值,如果没有办法被N整除,输出"*"。
思路:
一个数位上的数值增加1后,对N取模时的贡献可以预处理出来。设为mod[MAX_N]。
先把整个十进制大数置成最小的合法状态,存在res中。(问号置为0或1)
此时的大数对N取模的值是可以计算的。设为val。
如果val为0,res中已经是最大的答案。
如果val不为0,从最右边的"?"开始往左枚举"?"。
维护一个vis数组,vis[k] = true表示已经访问过的问号当中,至少有一种填法使得整个大数对N取模的余数为k。
如果vis[N-val] = true,说明找到了一种填法。因为是从右往左枚举"?"的,第一次得到vis[N-val] = true时,几乎就是最小的了。考虑到当前数位填的数相同时,可能在当前的"?"右边的"?"填数不同,可能导致最小值不同,所以要额外judge一下。更新vis数组时,记录路径,在vis[N-val] = true时,反演路径,对应修改res中对应数位的数值即可。
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; const int MAX_N = 1000 + 5; const int INF = 0x3f3f3f3f; int N;//取模 int len;//S的长度 char S[MAX_N], res[MAX_N]; int mod[MAX_N];//数位取模的值 int val;//work构造模N为val的值 bool vis[MAX_N], tmpvis[MAX_N];//vis[i]能否构造模N为i的值 int fat[MAX_N], fatnum[MAX_N], fatlog[MAX_N];//记录路径 inline bool judge(int pos, int l, int n, int f) { if (l == fatlog[fat[pos]]) { return n < fatnum[fat[pos]]; } return l < fatlog[fat[pos]]; } bool work() { memset(vis, false, sizeof vis); vis[0] = true; fat[0] = -1; fatnum[0] = 0; fatlog[0] = 0; if (vis[val]) return true; for (int i = 1; i <= len; i++) { int p = len-i; if (isdigit(S[p])) continue; memcpy(tmpvis, vis, sizeof vis); for (int j = 1+(i==len); j <= 9; j++) { for (int k = 0; k < N; k++) if(vis[k]) { int pos = ( k+mod[i]*(j - (i==len)) )%N; if (!tmpvis[pos] || judge(pos, i, j, k)) { tmpvis[pos] = true; //可能同时有很多边找到了?不可能。 fat[pos] = k; fatnum[pos] = j; fatlog[pos] = i; } } if (tmpvis[val]) return true; } memcpy(vis, tmpvis, sizeof vis); } return false; } int main() { // freopen("testdata.txt", "r", stdin); while (~scanf("%s%d", S, &N)) { len = strlen(S); mod[1] = 1%N; for (int i = 2; i <= len; i++) { mod[i] = mod[i-1]*10%N; } val = 0; for (int i = 1; i <= len; i++) { int p = len-i; res[p] = S[p]; if (isdigit(S[p])) { val += (S[p]-'0')*mod[i]%N, val %= N; } else if (S[p] == '?') { if (i == len) { val += mod[i]%N, val %= N; res[p] = '1'; } else { res[p] = '0'; } } } val = (N-val)%N; res[len] = '