牛牛的呱数
传送门
题意:n个大数,每个可选无限个,人选若干个,拼凑成p的倍数。求最短的这样的拼凑数的长度。
思路:
首先,遇到大数+整除我们就预处理每个大数
m
o
d
(
p
)
mod(p)
mod(p)的值和大数的长度。遇到拼接我们就预处理10的倍数
m
o
d
(
p
)
mod(p)
mod(p)的值们。
然后,我们面临的问题就是,如何组合这些大数,使得
m
o
d
(
p
)
mod(p)
mod(p)为0且最短。
菜鸡心路历程:首先想到了BFS,但是不会写(事实证明用Dijkstra可以)。然后想到了DP,但是不会写for循环(后来知道关键点在于,每个大数可选择0-p个,对答案的贡献只有p种,但是还是(@_@;)不知道怎么处理长度)。其实这道题就是比较模板的Dijkstra+DP。我上一篇文章刚刚做过,这次居然还没想到真是菜死了。
后来才知道,原来这种匹配组合方式,寻找最优解的作法,用BFS做,就是图论的最短路问题。个人用Dijkstra比较直观。
d
p
[
i
]
dp[i]
dp[i]表示,
m
o
d
mod
mod值为
i
i
i的可能的拼接数中最短的那个的长度。
队列状态:
m
o
d
mod
mod、
l
e
n
len
len分别存此状态的
m
o
d
mod
mod和长度。
队列中按照长度从小到大排序。
具体见代码。
顺便贴两个其他方法的大佬题解:
大佬的DP题解
大佬的floyd题解
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1000005;
const int inf=0x3f3f3f3f;
char s[maxn];//存当前大数
int dp[204];
int ten[maxn];//记录10的倍数的mod
int rmd[104];//记录n个大数的mod
int len[103];//记录n个大数的长度
struct node{
int mod,len;//队列状态。
};
//优先队列比较函数
struct cmp{
bool operator()(node a,node b){
//注意,优先队列本来是从大到小,所以这里反着写
return a.len>b.len;
}
};
int main(){
int n,p;
scanf("%d%d",&n,&p);
//处理10的倍数的mod
ten[0]=1;
for(int i=1;i<maxn;i++) ten[i]=(ten[i-1]*10)%p;
//处理大数的mod和长度
for(int i=1;i<=n;i++){
scanf("%s",s+1);
len[i]=strlen(s+1);
for(int j=1;j<=len[i];j++){
rmd[i]=(rmd[i]*10+(s[j]-'0'))%p;
}
}
//全部更新为inf
memset(dp,inf,sizeof(dp));
priority_queue<node,vector<node>,cmp> pq;
//初始化已有数值的dp
for(int i=1;i<=n;i++)
dp[rmd[i]]=min(dp[rmd[i]],len[i]);
//把n个初始状态推进去
for(int i=0;i<p;i++)
if(dp[i]!=inf)
pq.push(node{i,dp[i]});
while(!pq.empty()){
node cur=pq.top();
pq.pop();
if(cur.mod==0){
printf("%d\n",cur.len);
return 0;
}
//每种大数都遍历一遍,因为每种都有无限个
for(int i=1;i<=n;i++){
node nex;
//把新大数加在最右边。
nex.mod=(cur.mod*ten[len[i]]%p+rmd[i])%p;
nex.len=cur.len+len[i];
if(dp[nex.mod]>nex.len){
dp[nex.mod]=nex.len;
pq.push(nex);
}
}
}
printf("-1\n");
}