POJ_3373_Changing Digits_DP

好想去游泳。

题意

一个不超过100位的数字n和一个不超10000的数字k,改变其中的一些数字(可以改变0个数字),得到一个新的数字m,满足下列条件:

  1. 长度和n相同,除0外无前缀0
  2. m可以被k整除
  3. m尽可能改变少的数字
  4. 3优先的情况下m尽可能小

IO

Input

There are multiple test cases for the input. Each test case consists of two lines, which contains n(1≤n≤10100) and k(1≤k≤104, k≤n) for each line. Both n and k will not contain leading zeros.

Output

Output one line for each test case containing the desired number m.

分析

做出来以后看了看题解,发现用搜索做的人还用了抽屉原理,然后推出来只需要改变5位就可以搜出来因此只搜最后五位。然而这道题条件的优先级是改变数量少优先于改变后数字小,因此并没有什么卵用。
我用的是DP+记录DP路径,这题的DP搜索顺序挺坑爹的。
dp[i][j]定义为到第i位,除k余数为j时需要改变的数量。
dp[i][j]=min(dp[i][j], dp[ii][jj]),遍历当前位的数字l来递推,ii,jj为另一个i, j编号
min操作使得递推肯定抱枕改变的位数最小。
由于需要使数值最小,高位数字需要更大权利,因此从后向前遍历i,每一层i选择当前数字l都从0到9遍历,余数最不重要放在最里层。i从后向前遍历的原因可以从终点那里看出来。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
#define MXL 110
#define MXK 10010
#define INF 0x3f3f3f3f
char a[MXL];
int k;
int dp[MXL][MXK];
int pre[MXL][MXK];
int rec[MXL][MXK];
char ans[MXL];
int anscur;
int main(){
    while(scanf("%s%d",a,&k)!=EOF){
        int len=strlen(a);
        anscur=0;
        memset(ans,0,sizeof(ans));
        memset(dp,0x3f,sizeof(dp));
        memset(pre,-1,sizeof(pre));
        dp[len][0]=0;
        int t=1;
        for(int i=len;i>0;--i){
            if(i!=len)  t*=10;
            t%=k;
            for(int l=0;l<10;++l)
                for(int j=0;j<k;++j)    if(dp[i][j]!=INF){
                    if(len>1&&i==1&&l==0)   continue;
                    int tem=1-(l==a[i-1]-'0');
                    if(dp[i][j]+tem<dp[i-1][(j+l*t)%k]){
                        dp[i-1][(j+l*t)%k]=dp[i][j]+tem;
                        pre[i-1][(j+l*t)%k]=j;
                        rec[i-1][(j+l*t)%k]=l;
                    }
                }
        }
        int nowl=0,nowj=0;
        while(nowl<len){
            ans[anscur++]=rec[nowl][nowj]+'0';
            nowj=pre[nowl][nowj];++nowl;
        }
        printf("%s\n",ans);
    }
    return 0;
}
点赞