【JZOJ A组&省选】 划分

3 篇文章 0 订阅
2 篇文章 0 订阅

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.

思路

需要先知道一点,假如 x[p]能够求出来,那么必定存在一对(i,j),满足 P mod k[i]=0,p mod k[j]=1相当于 p=a1*k[i]=a2*k[j]+1

即 a1*k[i]-a2*k[j]=1,这个就可以用扩展欧几里得算法来求通解,并且我们可以知道,有解的条件必定是gcd(k[i],k[j])=1. 但是,这样做会重复计算。考虑容斥原理。

找出所有类似这样的方程
P mod k[i1]=0,P mod k[i2]=0,P mod k[i3]=0, ……
P mod k[j1]=1,P mod k[j2]=1,P mod k[j3]=1, ……
事实上,等价于满足两个方程。
P mod lcm(k[i1],k[i2],…)=0,P mod lcm(k[j1],k[j2],…)=1
这个方程的解的个数可以用扩展欧几里得来求。

这个可以用扩展欧几里得来解,然后对于一组解的可求出P的方案数为ki∗x∈[1,n]

代码

#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
int i,n,m,k[12];
ll d,x,y,ans;
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=y-x*(a/b);
}
ll gcd(ll x,ll y) 
{
    return y?gcd(y,x%y):x;
}
ll lcm(ll x,ll y)
{
    return x*y/gcd(x,y);
}
void dfs(int t,ll cnt1,ll cnt2,ll x1,ll x2,ll p)
{
    if (x1>n||x2>n) return;
    if (t>m)
    {

        if (cnt1==0||cnt2==0) return;
        exgcd(x1,x2,d,x,y);
        if(d<0) x=-x,y=-y,d=-d; 
        if(d>1) return;
        x=(x%x2+x2)%x2;
        ans+=p*((1ll*n/x1-x)/x2+(x1*x<=n));
        return;
    }
    dfs(t+1,cnt1,cnt2,x1,x2,p);
    dfs(t+1,cnt1+1,cnt2,lcm(x1,k[t]),x2,-p);
    dfs(t+1,cnt1,cnt2+1,x1,lcm(x2,k[t]),-p);
}
int main()
{
    freopen("sazetak.in","r",stdin); freopen("sazetak.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++) scanf("%d",&k[i]);
    k[++m]=n;
    dfs(1,0,0,1,1,1);
    printf("%lld",ans);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值