墨墨的等式

题目描述

分析

40pts

一道很巧妙的题目,果然不愧是往届集训队的题目,在网上看了一下,似乎是一道同余最短路)。

首先看到题目,可能会以为很难的数论,根本看不出来是一道最短路。分析一下题目,首先可以想到一个暴力做法,直接完全背包。可以推出方程:
f [ j ] = f [ j ] ∣ f [ j − a [ i ] ] f[j]=f[j] | f[j - a[i]] f[j]=f[j]f[ja[i]]
这是一个只能得 40 p t s 40pts 40pts的做法,这里就不再赘述。

100pts

进入正题。我们需要更优的算法,于是我们开始解剖这个式子。
因为每个 x i x_i xi都是正整数,所以当我们得到一个 b b b时,我们同样也可以得到 b + x i b+x_i b+xi,对于每一个 a i a_i ai都是如此,也就转化为了找小于 m i n ( a i ) min(a_i) min(ai)有哪些数,再在当前每一个可以得到的数上加上k倍的 a i a_i ai判断是否是在 [ l , r ] [l,r] [l,r]里。

M i n n Minn Minn m i n ( a i ) min(a_i) min(ai)
为了找到这些小于 M i n n Minn Minn基准数,我们可以用最短路。因为我们知道对于每一个 i i i,我们都可以加一个 a [ j ] a[j] a[j]使他成为一个数 i + a [ j ] i+a[j] i+a[j],然而 i + a [ j ] i+a[j] i+a[j]的基准数又是 ( i + a [ j ] ) m o d ( M i n n ) (i+a[j])mod(Minn) (i+a[j])mod(Minn),于是我们就可以连一条有向边,从 i i i变成 ( i + a [ j ] ) m o d ( M i n n ) (i+a[j])mod(Minn) (i+a[j])mod(Minn),他的代价是 a [ j ] a[j] a[j],也就是他需要加 a [ j ] a[j] a[j]才能变成以 ( i + a [ j ] ) m o d ( M i n n ) (i+a[j])mod(Minn) (i+a[j])mod(Minn)为基准数的一个数。我们不难知道,对于每一个 i i i,从 0 0 0走到 i i i就是他最小代价

最后,我们已经找到了每一个基准数,就可以很简单的手推一下 r r r l − 1 l-1 l1的个数,答案就是相减。

code

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#define int long long
using namespace std;
const int MAXN = 5e5 + 5;
int n,l,r,a[MAXN],cnt,num,dis[MAXN];
int Minn = 0x3f3f3f3f,head[MAXN];
bool vis[MAXN];
struct node{
	int val,to,Next;
}edge[MAXN * 12];
struct data{
	int u,w;
	data(){}
	data(int U,int W){u = U,w = W;}
	friend bool operator < (data x,data y) {return x.w > y.w;}
};
priority_queue<data> Q;
void Add(int x,int y,int z) {
	edge[++num].Next = head[x];
	edge[num].to = y;
	edge[num].val = z;
	head[x] = num;
}
void dijkstra() {
	memset(dis,0x3f,sizeof(dis));
	Q.push(data(0,0));
	dis[0] = 0;
	while(!Q.empty()) {
		data f = Q.top();
		Q.pop();
		if(vis[f.u]) continue;
		vis[f.u] = 1;
		for(int i = head[f.u];i;i = edge[i].Next) {
			int To = edge[i].to;
			if(dis[To] > dis[f.u] + edge[i].val) {
				dis[To] = dis[f.u] + edge[i].val;
				Q.push(data(To,dis[To]));
			}
		}
	}
}
int solve(int x) {
	int ans = 0;
	for(int i = 0;i < Minn;i ++)
	if(dis[i] <= x) ans += (x - dis[i]) / Minn + 1;
	return ans;
}
signed main() {
	scanf("%lld %lld %lld",&n,&l,&r);
	for(int i = 1;i <= n;i ++) {
		int x;
		scanf("%lld",&x);
		if(!x) continue;
		Minn = min(Minn,x);
		a[++cnt] = x;
	}
	for(int i = 0;i < Minn;i ++)
	for(int j = 1;j <= cnt;j ++)
	if(a[j] != Minn)
	Add(i,(i + a[j]) % Minn,a[j]);
	dijkstra();
	printf("%lld",solve(r) - solve(l - 1));
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值