Problem
有一个未知的序列x,长度为n。它的K-划分序列y指的是每连续K个数的和得到划分序列,y[1]=x[1]+x[2]+….+x[K],y[2]=x[K+1]+x[K+2]+….+x[K+K]….。若n不被K整除,则y[n/K+1]可以由少于K个数加起来。比如n=13,K=5,则y[1]=x[1]+…+x[5],y[2]=x[6]+….+x[10],y[3]=x[11]+x[12]+x[13]。若小A只确定x的K[1]划分序列以及K[2]划分序列….K[M]划分序列的值情况下,问她可以确定x多少个元素的值。
Hint
对于20%的数据,3 <= N <= 2000,M<=3。
对于40%的数据,3 <= N <= 5*10^6。
对于100%的数据,3 <= N <= 10^9 , 1 <= M <= 10,2 <= K[i] < N。
Solution
40points:暴力
- 首先,我们可以发现一个性质:若x[i]能被确定,充要条件为有区间的右端点在i和i-1。
- 先证充分性:如果有,那么 的值以及 的值均可确定,两式相减即为x[i]。
- 再证必要性:如果某个区间覆盖了[l,r](l<i&&i<r),又有两个区间[l,i-1]和[i+1,l]把[l,r]分割了,那看上去也可以;但是既然有区间[i+1,l],肯定也有右端点在i的区间(它的前一个区间)。
- 因此,我们可以设一个布尔数组p,p[i]表示是否存在右端点为i的区间。
- 对于每个K[i],暴力枚举它的倍数,更新p数组。
- 最后,扫一遍p数组, 即为答案。
- 时间复杂度: 。
Code
#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int N=5e6+1;
int i,j,n,m,k[11],ans;
bool p[N];
int main()
{
freopen("sazetak.in","r",stdin);
freopen("sazetak.out","w",stdout);
scanf("%d%d",&n,&m);
fo(i,1,m)
{
scanf("%d",&k[i]);
fo(j,1,n/k[i]) p[k[i]*j]=1;
}
p[n]=1;
fo(i,1,n) ans+=(p[i]&&p[i-1]);
printf("%d",ans);
}
100points:容斥+扩展中国剩余定理+扩展欧几里得
- 上面所述的性质,装逼点写就是:若x[P]能求出,当且仅当存在一组(i,j),满足 。
- 上式可转化为 ,即 (因为a2为不定的系数,且可以为负数,所以不必在意其正负性)。这个可以使用扩展欧几里得求通解,详见下文。
- 但是,对于 ,k1与k2、k3与k2都会计算,这时,就可能存在P值重复的情况。
- 应往容斥的方面想。
- 可以dfs出k1、k2的集合。设n1=|k1集合|,n2=|k2集合|,此时,我们得到了下列n1+n2个同余方程:
- 考虑使用扩展中国剩余定理将其合并。
- 但是,观察到该方程组的特殊性,我们知道
,并且
。因此,可以直接将上述方程组合并为下列两个方程:
- 令 。根据上述转化,可得方程: 。
- 注意:这个s1、s2可能很大(当ki都两两互质时,lcm即为乘积),但只要超过n就没有意义。因为我们是想用它们去覆盖n个位置。
- 下面就要开始搞事了。
- 根据裴蜀定理,上述方程存在整数解,当且仅当 。那么囿于1的约数只有1,所以实际上是当且仅当 。
- 然后,我们求解出一组x和y。但x、y可能为负,我们要使它们变成最小的正整数。
- 显然,若 成立,则 也成立。因此,x可以加上任意个s2,y可以减去任意个s1。
- 首先,我们将x增至最小的正整数,也即
x=(x%s2+s2)%s2;
此时,x*s1>0。我们又知道,x每次加s2,都有对应的唯一的y,因此,我们求的就是所有满足0<x*s1<n的x的个数。 - x最多可以增加 次。而设若x*s1≤n,则它不增加也是满足条件的。因此,贡献为 。
- 别忘了我们正在容斥。
- 对于每种方案,容斥系数应为 。
- 证明详见这位dalao的博客:https://blog.csdn.net/Cold_Chair/article/details/81566102。
- 然后这道题还有一个坑点。
- 假如我们知道 ,那也能知道x[n]。因为M≥1,一定会有一种划分,我们直接将那种划分的所有和加起来,就是 。
- 而当所有划分的K都不整除n时,程序就以为我们不知道
。因此一开始需要打这么一行代码:
k[++m]=n;
- 时间复杂度: 。
Code
#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
int i,n,m,k[12];
ll d,x,y,ans;
ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
inline ll lcm(ll x,ll y){return x*y/gcd(x,y);}
void exgcd(ll a,ll b,ll&d,ll&x,ll&y)
{
if(!b){d=a;x=1;y=0;}
else {exgcd(b,a%b,d,y,x);y-=x*(a/b);}
}
void dfs(int t,ll s1,ll s2,ll f)
{
if(max(s1,s2)>n) return;
if(t>m)
{
if(s1==1||s2==1) return;
exgcd(s1,s2,d,x,y);
if(d<0) {x=-x; y=-y; d=-d;}
if(d>1) return;
x=(x%s2+s2)%s2;
ll tmp=(1ll*n/s1-x)/s2+(s1*x<=n);
ans+=f*tmp;
return;
}
dfs(t+1,s1,s2,f);
dfs(t+1,lcm(s1,k[t]),s2,-f);
dfs(t+1,s1,lcm(s2,k[t]),-f);
}
int main()
{
freopen("sazetak.in","r",stdin);
freopen("sazetak.out","w",stdout);
scanf("%d%d",&n,&m);
fo(i,1,m) scanf("%d",&k[i]);
k[++m]=n;
dfs(1,1,1,1);
printf("%lld",ans);
}