同余最短路(3道母题)

先分享一些讲同余最短路的博客:
【算法笔记】一步一步推出来的同余最短路优化思路(千字长文,超详细)
P2662 牛场围栏(同余最短路)
同余最短路小结

虽然解题的算法是最短路,但思维方式还是动态规划,前提知识:(队列优化的)dij算法、spfa、动态规划入门


问题形式

1、给出n个整数,问用这n个整数可以拼出多少整数
2、给出n个整数,问最小不能拼出的整数是多少
3、给出n个整数,问最大不能拼出的整数是多少

套路是:物品无限+物品体积小+背包非常大=最短路

所以思路完善是:暴搜 ——> 完全背包 (dp) ——> 最短路(Dijkstra或spfa)

基本模型:动态规划
转移方程: f[(i + y) % mod] = f[i] + y

以此映射到最短路的dis[v] = dis[u] + val中


一些我自己帮助理解这个概念的辅助理解:

q1:为什么以最小的数值为基底?

以遍历 基底 以内所有为基础,基底越小越好,实际上,复杂度也在这个方法上降为 基底 的数据范围。

q2:为什么遍历各个点时,取的条件为(h>=d[i])(h为最高高度)?

问题是凑数,所以没有相减。到同余 i 时,它是从 d[i] 这个位置减去 k * x(x为基底,k为正整数)到 小于x数的以内的。

如果h < d[i],表示 i 不能由 a * y + b * z 表示,虽然 i == A * y + B * z - k * x ,但它不能在最高高度内表示。

当然有些 d[i] == inf ,表示这个点完全不能到达,通常这几个数的gcd不等于1会出现这种情况。

q3:最小/大不能拼出的整数怎么得到?

假如基底为t,最高高度为h,且各个整数最后的gcd为1。
最大的:把所有的d[i]-t求出来,取其中最大值。

for(int i=0;i<t;i++)	sum=max(sum,d[i]-t);

最小的:把所有最小的d[i]-k*t(k为正整数,且这个式子的值大于0)求出来,取其中最小值。

sum=inf;
for(int i=0;i<t;i++)	sum=min(sum,d[i]-(d[i]/t)*t);

具体问题具体分析。


跳楼机

我的部分代码:

const int maxn=2e5+7;
ll h,x,y,z,sum,d[maxn],head[maxn],cnt;
bool vis[maxn];
struct edge{	ll to,w,next;	}e[maxn*5];
void add(ll u,ll v,ll w){
	e[++cnt].w=w;	e[cnt].to=v;
	e[cnt].next=head[u];	head[u]=cnt;
}
void spfa(){
	memset(d,inf,sizeof(d));	memset(vis,0,sizeof(vis));
	d[1]=1;	vis[1]=1;
	queue<ll>q;	q.push(1);
	while(!q.empty()){
		ll u=q.front();	q.pop();	vis[u]=0;
		for(ll i=head[u];i;i=e[i].next){
			int v=e[i].to,w=e[i].w;
			if(d[v]>d[u]+w){
				d[v]=d[u]+w;
				if(!vis[v]){	q.push(v);	vis[v]=1;	}
			}	
		}
	}
}
int main(){
	h=read();	x=read();	y=read();	z=read();
	if(x==1||y==1||z==1){
		printf("%lld",h);
		return 0;
	}
	for(int i=0;i<x;i++){
		add(i,(i+y)%x,y);
		add(i,(i+z)%x,z);
	}
	spfa();
	for(int i=0;i<x;i++)	if(h>=d[i])	sum+=(h-d[i])/x+1;
	printf("%lld",sum);
}

牛场围栏

不存在的情况有两种,其中不存在这个最大值时,你如果跑d[]数值,遇到d[i]>=inf也不是不可以,但其实这种做法大概相当于猜测利用了数据比较小,最好用gcd判断。

求不能凑出的最大数的时候,我们要先考虑d数组的意义。d[i]即为其他数字能凑出来的%t=i的最小数字,那么d[i]+t,d[i]+2t,d[i]+3t… d[i]以上跳所有个t都能达到。
那么%t=i的数字,最大凑不出来的就是d[i]-t。(1、前面验证a[0]-m>1 -------2、d[i]-k*t 也会凑不出(其中k为正整数))
那么很显然了,把所有的d[i]-t求出来,取其中最大值。

const int maxn=5e6+7;
int gcd(int a,int b){	return b?gcd(b,a%b):a;	}
int n,m,a[3000],head[3000],cnt,d[3000],t,sum;
bool vis[3000];
struct edge{	int to,w,next;	}e[maxn];
void add(int u,int v,int w){
	e[++cnt].w=w;	e[cnt].to=v;
	e[cnt].next=head[u];	head[u]=cnt;
}
void spfa(){
	memset(d,inf,sizeof(d));	memset(vis,0,sizeof(vis));
	d[0]=0;	vis[0]=1;
	queue<int>q;	q.push(0);
	while(!q.empty()){
		int u=q.front();	q.pop();	vis[u]=0;
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].to,w=e[i].w;
			if(d[v]>d[u]+w){
				d[v]=d[u]+w;
				if(!vis[v]){	q.push(v);	vis[v]=1;	}
			}
		}
	}
}
int main(){
	n=read();	m=read();
	for(int i=0;i<n;i++){
		a[i]=read();
		if(!i)	t=a[i];
		else	t=gcd(a[i],t);
	}
	sort(a,a+n);
	if(a[0]-m<=1||t!=1){
		cout<<-1;
		return 0;
	}
	t=a[0]-m;
	for(int i=0;i<n;i++)	for(int j=0;j<t;j++)	for(int k=a[i]-m;k<=a[i];k++)	add(j,(j+k)%t,k);
	spfa();
	for(int i=0;i<t;i++)	sum=max(sum,d[i]-t);
	cout<<sum;
}

[国家集训队]墨墨的等式

看数据比较大,用了队列优化的dij算法代替spfa算法(spfa我也试了,会超时)。

const int maxn=5e5+7;
ll n,l,r,t,a[20],head[maxn],d[maxn],sum,cnt;
struct edge{	ll to,w,next;	}e[maxn*12];
void add(ll u,ll v,ll w){
	e[++cnt].w=w;	e[cnt].to=v;
	e[cnt].next=head[u];	head[u]=cnt;
}
struct node{
	int x;	ll dis;
	node(int xx=0,ll dd=0):x(xx),dis(dd){}
	bool operator <(const node &b)const{	return dis>b.dis;	}
};
void dij(){
	memset(d,inf,sizeof(d));	d[0]=0;
	priority_queue<node>q;	q.push(node(0,0));
	while(!q.empty()){
		int u=q.top().x;	ll temp=q.top().dis;	q.pop();
		if(temp!=d[u])	continue;
		for(ll i=head[u];i;i=e[i].next){
			ll v=e[i].to,w=e[i].w;
			if(d[v]>d[u]+w){
				d[v]=d[u]+w;
				q.push(node(v,d[v]));
			}
		}
	}
}
int main(){
	n=read();	l=read();	r=read();
	for(int i=0;i<n;i++)	a[i]=read();
	sort(a,a+n);	t=a[0];
	if(t==1){
		cout<<r-l+1<<endl;
		return 0;
	}
	for(int i=0;i<n;i++)	for(int j=0;j<t;j++)	add(j,(j+a[i])%t,a[i]);
	dij();	ll x=0,y=0;
	for(int i=0;i<t;i++){
		if(d[i]<=r)	x+=(r-d[i])/t+1;
		if(d[i]<l)	y+=(l-1-d[i])/t+1; 
	}
	sum=x-y;
	printf("%lld",sum);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值