【JZOJ5796】【2018提高组】模拟A组&省选 划分(容斥+扩展中国剩余定理+扩展欧几里得)

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。
  • 先证充分性:如果有,那么j=1ix[j] 的值以及j=1i1x[j] 的值均可确定,两式相减即为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数组,i=1np[i]andp[i1] 即为答案。

  • 时间复杂度:O((i=1mnki)+n)
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),满足P0(mod ki),P1(mod kj)
  • 上式可转化为P=a1ki=a2kj+1 ,即a1ki+a2kj=1 (因为a2为不定的系数,且可以为负数,所以不必在意其正负性)。这个可以使用扩展欧几里得求通解,详见下文。
  • 但是,对于P0(mod k1),P1(mod k2),P0(mod k3) ,k1与k2、k3与k2都会计算,这时,就可能存在P值重复的情况。
  • 应往容斥的方面想。

  • 可以dfs出k1、k2的集合。设n1=|k1集合|,n2=|k2集合|,此时,我们得到了下列n1+n2个同余方程:
    P0(mod ki1)
    P0(mod ki2)
    ...
    P0(mod kin1)
    P1(mod kj1)
    P1(mod kj2)
    ...
    P1(mod kjn2)
  • 考虑使用扩展中国剩余定理将其合并。
  • 但是,观察到该方程组的特殊性,我们知道1xn1 kix|P ,并且1xn2 kjx|P1 。因此,可以直接将上述方程组合并为下列两个方程:
    P0(mod lcm(ki1,ki2,...,kin1))
    P1(mod lcm(kj1,kj2,...,kjn2))
  • s1=lcm(ki1,ki2,...,kin1),s2=lcm(kj1,kj2,...,kjn2) 。根据上述转化,可得方程:a1s1+a2s2=1
  • 注意:这个s1、s2可能很大(当ki都两两互质时,lcm即为乘积),但只要超过n就没有意义。因为我们是想用它们去覆盖n个位置。
  • 下面就要开始搞事了。

  • 根据裴蜀定理,上述方程存在整数解,当且仅当gcd(s1,s2)|1 。那么囿于1的约数只有1,所以实际上是当且仅当gcd(s1,s2)=1
  • 然后,我们求解出一组x和y。但x、y可能为负,我们要使它们变成最小的正整数。
  • 显然,若xs1+ys2=1 成立,则(x+ns2)s1+(yns1)s2=1(nZ) 也成立。因此,x可以加上任意个s2,y可以减去任意个s1。
  • 首先,我们将x增至最小的正整数,也即x=(x%s2+s2)%s2;此时,x*s1>0。我们又知道,x每次加s2,都有对应的唯一的y,因此,我们求的就是所有满足0<x*s1<n的x的个数。
  • x最多可以增加ns1 次。而设若x*s1≤n,则它不增加也是满足条件的。因此,贡献为ns1+[xs1n]


  • 然后这道题还有一个坑点。
  • 假如我们知道i=1n1x[i] ,那也能知道x[n]。因为M≥1,一定会有一种划分,我们直接将那种划分的所有和加起来,就是i=1nx[i]
  • 而当所有划分的K都不整除n时,程序就以为我们不知道i=1nx[i] 。因此一开始需要打这么一行代码:k[++m]=n;

  • 时间复杂度:O(m+3mlog2n)

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);
}
发布了114 篇原创文章 · 获赞 52 · 访问量 4万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章