[容斥][数论]JZOJ 5796 划分

Description

有一个未知的序列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多少个元素的值。
 

Input

第一行输入两个正整数n,M。
第二行输入M个正整数表示K[1],K[2].....K[M]。

Output

输出1个整数,表示能确定的元素
 

Sample Input

【输入样例1】
3 1
2
【输入样例2】
6 2
2 3
【输入样例3】
123456789 3
5 6 9

Sample Output

【输出样例1】
1
【输出样例2】
2
【输出样例3】
10973937
 

Data Constraint

对于20%的数据,3 <= N <= 2000,M<=3。
对于40%的数据,3 <= N <= 5*10^6。
对于100%的数据,3 <= N <= 10^9 , 1 <= M <= 10,2 <= K[i] < N。
 

Hint

【样例1解释】
小A知道x的2-划分序列,即分别知道x[1]+x[2],x[3]的值。
小A可以知道x[3]的值。
【样例2解释】
小A知道x的2-划分序列,即分别知道x[1]+x[2],x[3]+x[4],x[5]+x[6] 的值。
小A知道x的3-划分序列,即分别知道x[1]+x[2]+x[3] ,x[4]+x[5]+x[6] 的值。
小A可以知道x[3],x[4]的值,个数为2.

分析

我们发现其实对于一个可以被识别的p(位置),一定有一对(i,j)

p%k[i]=0,p%k[j]=1

也就是:a*k[i]+b*k[j]=1 (mod p)这样一个同余方程

那么我们只要去重就行了,考虑容斥

那么两个互不相干的集合s1,s2的方程如下:

p%s1[0]=0 p%s1[1]=0 p%s1[2]=0 ...

p%s2[0]=1 p%s2[1]=1 p%s2[2]=1 ...

整个同余方程组可以整合为:a*lcm(|s1|)+b*lcm(|s2|)=1 (mod p),容斥系数为(-1)|s1|+|s2|

然后这个同余方程在题目范围内的解数为:(n div lcm(|s1|-a+lcm(|s2|))/|s2|(a为正整数且<n)

然后枚举时注意一下lcm(|s1|)和lcm(|s2|)都不能大于n,gcd(lcm(|s1|),lcm(|s2|))要等于1才有解

各种零注意判断一下

#pragma GCC optimize(3)
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
int m;
ll n;
ll a[12];
ll ans;

inline ll Exgcd(ll a,ll b,ll &x,ll &y) {
    if (!b) {
        x=1;y=0;
        return a;
    }
    int ret=Exgcd(b,a%b,y,x);
    y-=a/b*x;
    return ret;
}

inline ll Lcm(ll a,ll b) {
    if (!a) return b;
    if (!b) return a;
    ll x,y;
    return a*b/Exgcd(a,b,x,y);
}

inline void Solve(int dep,ll s1,ll s2,int cnt) {
    if (s1>n||s2>n) return;
    if (dep==m) {
        if (s1==0||s2==0) return;
        ll x,y;
        if (Exgcd(s1,s2,x,y)!=1) return;
        x=(x%s2+s2)%s2;
        ans+=(cnt%2?-1ll:1ll)*(n/s1-x+s2)/s2;
        return;
    }
    Solve(dep+1,Lcm(s1,a[dep]),s2,cnt+1);
    Solve(dep+1,s1,Lcm(s2,a[dep]),cnt+1);
    Solve(dep+1,s1,s2,cnt);
}

int main() {
    freopen("sazetak.in","r",stdin);
    freopen("sazetak.out","w",stdout);
    scanf("%lld%d",&n,&m);
    for (register int i=0;i<m;i++) scanf("%lld",&a[i]);
    a[m++]=n;
    Solve(0,0,0,0);
    printf("%lld",ans);
    fclose(stdin);fclose(stdout);
}
View Code

 

posted @ 2018-08-10 20:51  Vagari  阅读(289)  评论(0编辑  收藏  举报