BZOJ 2118 墨墨的等式 最短路 同余类分析
一. 题目
墨墨突然对等式很感兴趣,他正在研究
a1x1+a2x2+…+anxn=B
存在非负整数解的条件,他要求你编写一个程序,给定
N
、
对于
100
的数据,
N≤12
,
0≤ai≤5∗105
,
1≤BMin≤BMax≤1012
。
二. 分析
求
[l,r]
等价于 求
[1,r]−[1,l−1]
。
即考虑一个数
B
,求所有不大于
首先的想法:
设
fi
表示能否凑到
i
,则有:
①边界:
②递推:
fi=fi−a1∨fi−a2∨...∨fi−an
。
然后枚举
0
到
然而
B
太大,会超时。
既然
方法①应该不行,考虑方法②。
注意到数据范围
这样的话,任何一个方案都属于其中的一个类。
这样 原问题 等价于 求满足下列两个条件的数的个数 :
①
②
a1x1+a2x2+...+an−1xn−1≤B
我们假如能求出一个类中能凑出的数的个数,将
A
个类的个数相加即为结果。
现在只用考虑求属于一个类的能凑出的数的个数。
假如可以凑出值
对于每一个类,我们知道上界
B
,假如也知道属于该类的能凑出的最小值
这可以在 O(1) 内求得,个数为 B−pA+1 。
现在要求每一类中最小的能凑出的值。
这用SPFA,Dijsktra等算法皆可求出。
三. 代码
(以前的代码,懒得重新写了)
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
typedef long long LL;
const int N=13;
const int M=500001;
const LL MAX=10000000000000;
int n; LL BMin,BMax;
int p[N],vtx;
LL dis[M]; int q[M],h,t,v[M];
inline LL query(LL w)
{
LL cnt=0;
for (int i=0;i<vtx;i++)
if (dis[i]<=w) cnt+=(w-dis[i])/vtx+1;
return cnt;
}
int main(void)
{
scanf("%d%lld%lld",&n,&BMin,&BMax);
for (int i=1;i<=n;i++) scanf("%d",&p[i]);
vtx=p[1]; for (int i=2;i<=n;i++) if (vtx>p[i]) vtx=p[i];
int now,nxt;
for (int i=1;i<vtx;i++) dis[i]=MAX;
v[q[t=1]=0]=1;
for (;h^t;)
{
v[now=q[h=h%vtx+1]]=0;
for (int r=1;r<=n;r++)
{
nxt=(dis[now]+p[r])%vtx;
if (dis[now]+p[r]<dis[nxt])
{
dis[nxt]=dis[now]+p[r];
if (!v[nxt])
{
v[nxt]=1;
q[t=t%vtx+1]=nxt;
}
}
}
}
printf("%lld\n",query(BMax)-query(BMin-1));
return 0;
}
四. 小结
1. 权值过大的调整
我们在使用基于权值的数组计算时,可能会应该目标太大而出现MLE和TLE的情况。
假如还是使用相同的方法,我们可以做如下几种常见的调整:离散化,按权值大小分块,同余类分析。
2. 同余类分析
当权值过大的时候,考虑使用这种方法。
这种问题求解的,通常是方案存在性问题和方案计数问题。
一般选取的同余值
A
,要满足:若方案值为