[ACM]【余数/Dijkstra/DP】牛客练习赛62 牛牛的呱数

牛牛的呱数

传送门
题意: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");
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值