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

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

题目描述

墨墨突然对等式很感兴趣,他正在研究 \(\sum_{i=1}^n a_ix_i=b\) 存在非负整数解的条件,他要求你编写一个程序,给定 \(n, a_{1\dots n}, l, r\),求出有多少 \(b\in[l,r]\) 可以使等式存在非负整数解。

输入格式

第一行三个整数 \(n,l,r\)

第二行 \(n\) 个整数 \(a_{1\dots n}\)

输出格式

一行一个整数,表示有多少 \(b\in[l,r]\) 可以使等式存在非负整数解。

输入输出样例 #1

输入 #1

2 5 10
3 5

输出 #1

5

说明/提示

对于 \(20\%\) 的数据,\(n \le 5\)\(r \le 10\)

对于 \(40\%\) 的数据,\(n \le 10\)\(r \le 10^6\)

对于 \(100\%\) 的数据,\(n \le 12\)\(0 \le a_i \le 5\times 10^5\)\(1 \le l \le r \le 10^{12}\)

结题报告

首先考虑暴力的做法
\(dp[i]\) 表示 $ \sum_{j=1}^n a_j \times x_j=i $ 是否可以表示
这是一个完全背包问题,转移方程为 \(dp[i]|=dp[i-a_j] ( 1<=j<=n )\)
对于 1E+18 量级 的 \(L\)\(R\) ,这样做显然不行

考虑 差分,$ ans[l,r]=ans[0,r]-ans[0,l-1] $
问题变为 求给定区间 $ [0,x] $ 中的方案数
\(mod\) 为 任意的 \(a_i\)
当 $ \sum_{j=1}^n a_j \times x_j=i $时
则有 $ \sum_{j=1}^n a_j \times x_j=i + k*mod $, \(k\)为任意自然数
这说明 当 \(i\) 合法时,与 \(i\) 同余的数 同样合法
同时,当 \(i\) 最小时,合法方案 最多
那么,只需要找到 每个余数\([0,mod-1]\) 最小的合法的 \(i\)
我们就可以计算 任意一个余数的合法方案数
\(dis[r]\) 表示 余数为\(r\) 时最小的 \(i\)
那么在 \([0,x]\) 内就有 \(( x-dis[r] )/mod+1\) 个合法方案

那么现在的问题是,如何求解 \(dis[r] (0<=r<mod)\)
显然 自然数\(0\) 总可以被表示,它的同余类总是有合法方案
考虑与 \(dp\) 类似的想法,我们考虑 各个同余类 之间的转移
显然 ,可以 从 余数\(r\) 通过加上任意一个\(a_j\) 转移到$ (r+a_j)%mod$
结合 \(0\)所属的同余类 总是合法
当我们从 \(0\) 开始转移时,就可以得出各个余数的合法的i

但是,经过上面分析,我们要求的实际是最小的 \(i\) ,即\(dis[r]\)
一个很好的表示转移的方法 是 用单向边表示
对于 余数\(r\) 加上任意一个\(a_j\) 转移到 \((r+a_j) \bmod mod\)
可以 建一条 \(r\)\((r+a_j) \bmod mod\) 权值 为 \(a_j\) 的单向边 , 表示 一次转移
这样就可以通过最短路算法,计算 每个同余类\(r\)\(dis[r]\)
就可以快速计算结果了

一般为了 减少边数以加快速度,\(mod\)取最小的 \(a_i\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF=(1E+12)+10;
const int N=5001100;

inline int read()
{
	int f=1,x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch))  { x=x*10+ch-'0';    ch=getchar(); }
	return f*x;
}

int n,m,L,R;
int a[N],g;


struct edge
{
    int next,to;
    int dis;
}e[N<<1];
int head[N],tot;
int mod=INF;

inline void add_edge(int u,int v,int w)
{
    e[tot]=(edge){ head[u],v,w };
    head[u]=tot++;
}

struct node
{
    int pos,dis;
    bool operator < (const node &x)const
    { return x.dis<dis; }
};
int dis[N];
bool vis[N];

void dijkstra(int s)
{
    priority_queue<node> q;
    memset(dis,0x3f,sizeof(dis));
    memset(vis,false,sizeof(vis));
    q.push( (node){ s,dis[s]=0 } );
    vis[s]=true;
    while(!q.empty())
    {
        int u=q.top().pos; q.pop();
        for(int i=head[u];~i;i=e[i].next)
        {
            int v=e[i].to;
            if(dis[v]>dis[u]+e[i].dis)
            {
                dis[v]=dis[u]+e[i].dis;
                if(!vis[v])
                {
                    vis[v]=true;
                    q.push( (node){ v,dis[v] } );
                }
            }
        }
    }
}

inline int calc(int x)
{
    int ans=0;
    for(int i=0;i<mod;i++)
     if(dis[i]<=x)
       ans+=(x-dis[i])/mod+1;
    return ans;
}

signed main()
{
    // 洛谷P2371 [国家集训队] 墨墨的等式
	freopen("equation.in","r",stdin);
	freopen("equation.out","w",stdout);
	memset(head,-1,sizeof(head));
    n=read(),L=read(),R=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();
        if(x) mod=min(mod,a[++m]=x);
    }
    n=m;
    for(int i=0;i<mod;i++)
     for(int j=1;j<=n;j++)
      if(a[j]!=mod)
        add_edge(i,(i+a[j])%mod,a[j]);
    dijkstra(0);
    printf("%lld",calc(R)-calc(L-1));
	return 0;
}
posted @ 2025-08-16 19:33  南北天球  阅读(12)  评论(0)    收藏  举报