POI X Sums
一个集合 SSS
内有 nnn
个正整数 {a1,a2,…,an}\{a_1, a_2, \ldots, a_n\}{a1,a2,…,an}
,S′S'S′
表示由集合 SSS
中的数相加所得的集合,每个数都可以用无数次。
有 qqq
次询问,询问一个 xxx
是否在 S′S'S′
内。存在输出 y
否则输出 n
。
n≤5000,q,ai,≤50000,x≤1017n \leq 5000, q, a_i, \leq 50000, x \leq 10^{17}n≤5000,q,ai,≤50000,x≤1017
.
输入 nnn
,然后输入 nnn
个数,再输入 qqq
,然后qqq
个 xxx
题意可以转化为:给定a数组,问式子a[1]*x[1]+a[2]*x[2]+……+a[n]*x[n]=k是否有正整数解。
其实看下来,这就是一个容量为k的完全揹包,判断是否能恰好装满。
叉姐的解释:
在 SSS
中找一个数,例如 a1a_1a1
. 一个关键性质是:如果 x∈S′x \in S'x∈S′
,那么x+a1∈S′x + a_1 \in S'x+a1∈S′
.
那么设 d[r]d[r]d[r]
表示所有模 a1a_1a1
于 rrr
的数中,最小的 ∈S′\in S'∈S′
的数。根据性质,d[r],d[r]+a1,d[r]+2a1,…∈S′d[r], d[r] + a_1, d[r] + 2a_1, \ldots \in S'd[r],d[r]+a1,d[r]+2a1,…∈S′
. 同时因为 d[r]d[r]d[r]
是最小的,所以同余类中 <d[r]< d[r]<d[r]
的数都 ∉S′\notin S'∉S′
.
具体求 d[∗]d[*]d[∗]
的过程是最短路。首先,d[0]=0d[0] = 0d[0]=0
。其次,可以用d[x]+aid[x] + a_id[x]+ai
去更新 d[(x+ai) mod a1]d[(x + a_i)\ \mathrm{mod}\ a_1]d[(x+ai) mod a1]
.
这里如果用 Dijkstra 的话,复杂度是 O(na1loga1)O(na_1 \log{a_1})
. 从这个角度上讲,a1a_1
应该取 SS
中最小的。
关键的部分就是求d[]数组的最短路,根据定义有d[ (r+a[i])%mod ]=min(d[ (r+a[i])%mod], d[r]+a[i] );
另一种用图论的最短路解释为:
设a1为最小数字,若方程a[1]*x[1]+a[2]*x[2]+……+a[n]*x[n]=k存在非负整数解,那么k+a1也必然有解。
建立a1个点的图,点编号为0到a1-1,i向(i+aj)%a1连边,边权为aj,求0到所有点的最短路,用dis数组存,如果dis[k%a1]<=k,那么k有解。
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <queue>
#include <vector>
#define P pair<int,int>
using namespace std;
const int ma=50010,INF=0x3f3f3f3f;
int n,m,a[ma],f[ma];
void solve()
{
sort(a,a+n);
m=a[0];
for(int i=1; i<m; ++i)
f[i]=INF;
priority_queue<P,vector<P>,greater<P> > q;
q.push(P(0,0));
P t;
int x;
while(!q.empty())
{
t=q.top();
q.pop();
if(f[t.second]<t.first) continue;
x=t.second;
for(int i=1; i<n; ++i)
if(f[x]+a[i]<f[(x+a[i])%m])
{
f[(x+a[i])%m]=f[x]+a[i];
q.push(P(f[(x+a[i])%m],(x+a[i])%m));
}
}
}
bool judge(int x)
{
return x>=f[x%m];
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
for(int i=0; i<n; ++i)
scanf("%d",&a[i]);
solve();
int q,x;
scanf("%d",&q);
while(q--)
{
scanf("%d",&x);
if(judge(x)) puts("TAK");
else puts("NIE");
}
}
return 0;
}
再看一题类似的,但是结果是求a[1]*x[1]+a[2]*x[2]+……+a[n]*x[n]=k没有正整数解的最大K。
牛场围栏
John计划为他的牛场建一个围栏,以限制奶牛们的活动。他有N种可以建造围栏的木料,长度分别是l1,l2…lN,每种长度的木料无限。修建时,他将把所有选中的木料拼接在一起,因此围栏的长度就是他使用的木料长度之和。但是聪明的John很快发现很多长度都是不能由这些木料长度相加得到的,于是决定在必要的时候把这些木料砍掉一部分以后再使用。不过由于John比较节约,他给自己规定:任何一根木料最多只能削短M米。当然,每根木料削去的木料长度不需要都一样。不过由于测量工具太原始,John只能准确的削去整数米的木料,因此,如果他有两种长度分别是7和11的木料,每根最多只能砍掉1米,那么实际上就有4种可以使用的木料长度,分别是6, 7, 10, 11。
Clevow是John的牛场中的最聪明的奶牛,John请她来设计围栏。Clevow不愿意自己和同伴在游戏时受到围栏的限制,于是想刁难一下John,希望John的木料无论经过怎样的加工,长度之和都不可能得到她设计的围栏总长度。
不过Clevow知道,如果围栏的长度太小,John很快就能发现它是不能修建好的。因此她希望得到你的帮助,找出无法修建的最大围栏长度。
格式
输入格式
输入的第一行包含两个整数N, M (1<N<100, 0<=M<3000),分别表示木料的种类和每根木料削去的最大值。以下各行每行一个整数li(1<li<3000),表示第i根木料的原始长度。
输出格式
输出仅一行,包含一个整数,表示不能修建的最大围栏长度。如果任何长度的围栏都可以修建或者这个最大值不存在,输出-1。
分析没有正整数解的情况:
1)数组a存在值为1的情况
2)某个点的最小路径不存在,如果f[x]不存在,那么f[x]+k*a[1]也不存在,可以到无限大,也就是没有确定的解
其他的情况都是存在的。
/*
真的是老泪纵横,这题用多组输入,结果有1 的情况,输出,完了我用continue,结果一直WA在第十个样例,一改回单租输入就AC了,我的内心是绝望的。
https://vijos.org/p/1054
*/
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#include <vector>
#define P pair<int,int>
using namespace std;
const int ma=3010,INF=0x3f3f3f3f;
int l[ma],f[ma],cnt;
bool vis[ma];
void solve()
{
int m=l[1];
memset(f,INF,sizeof(f));
f[0]=0;
priority_queue<P,vector<P>,greater<P> > q;
q.push(P(0,0));
int x;
P t;
while(!q.empty())
{
t=q.top();
q.pop();
x=t.second;
if(f[x]<t.second) continue;
for(int i=1; i<=cnt; ++i)
{
if(f[x]+l[i]<f[(x+l[i])%m])
{
f[(x+l[i])%m]=f[x]+l[i];
q.push(P(f[(x+l[i])%m],(x+l[i])%m));
}
}
}
for(int i=0; i<m; ++i)
if(f[i]==INF)
{
puts("-1");
return;
}
int ans=0;
for(int i=0; i<m; ++i)
ans=max(ans,f[i]-m);
printf("%d\n",ans);
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
memset(vis,false,sizeof(vis));
int x;
for(int i=0; i<n; ++i)
{
scanf("%d",&x);
for(int j=(x-m>0?x-m:1); j<=x; ++j)
vis[j]=true;
}
if(vis[1])
{
puts("-1");
return 0;
}
cnt=0;
for(int i=1; i<=3000; ++i)
if(vis[i]) l[++cnt]=i;
solve();
return 0;
}
还有一个类似的题
2118: 墨墨的等式
Description
墨墨突然对等式很感兴趣,他正在研究a1x1+a2y2+…+anxn=B存在非负整数解的条件,他要求你编写一个程序,给定N、{an}、以及B的取值范围,求出有多少B可以使等式存在非负整数解。
Input
输入的第一行包含3个正整数,分别表示N、BMin、BMax分别表示数列的长度、B的下界、B的上界。输入的第二行包含N个整数,即数列{an}的值。
Output
输出一个整数,表示有多少b可以使等式存在非负整数解。
这个要求给出的[ L,R ]区间能够使a[1]*x[1]+a[2]*x[2]+……+a[n]*x[n]=k有非负整数解的k的个数。
计算个数的过程如下:
LL cal(LL x)
{
LL ans=0;
for(int i=0;i<m;++i)
{
if(x>=f[i])
ans+=1ll*(x-f[i])/m+1;
}
return ans;
}
然后用cal(R)-cal(L-1)就是结果。代码如下:
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#include <vector>
#define LL long long
#define P pair<LL,int>
using namespace std;
const int ma=5e5+10;
const LL INF=0x3f3f3f3f3f3f3f3f;
int l[ma],n,m;
LL beg,en,f[ma];
LL cal(LL x)
{
LL ans=0;
for(int i=0;i<m;++i)
{
if(x>=f[i])
ans+=1ll*(x-f[i])/m+1;
}
return ans;
}
void solve()
{
m=l[1];
int k=1;
while(!m)
m=l[++k];
for(int i=1;i<m;++i)
f[i]=INF;
f[0]=0;
priority_queue<P,vector<P>,greater<P> > q;
q.push(P(0,0));
int x;
P t;
while(!q.empty())
{
t=q.top();
q.pop();
x=t.second;
if(f[x]<t.second) continue;
for(int i=1; i<=n; ++i)
{
if(f[x]+l[i]<f[(x+l[i])%m])
{
f[(x+l[i])%m]=f[x]+l[i];
q.push(P(f[(x+l[i])%m],(x+l[i])%m));
}
}
}
}
int main()
{
scanf("%d%lld%lld",&n,&beg,&en);
for(int i=1; i<=n; ++i)
scanf("%d",&l[i]);
sort(l+1,l+n+1);
if(!l[n])
{
printf("0\n");
return 0;
}
solve();
printf("%lld\n",cal(en)-cal(beg-1));
return 0;
}