JOZJ5796. 2018.08.10【2018提高组】模拟A组&省选 划分

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.

题解

题目给出某些位置的前缀和,然后问有多少个数是可以求出来的。
设前缀和为 si s i
如果这个位置x能被表示出来,当且仅当知道 sx s x sx1 s x − 1
转化一下:
x= kia k i ∗ a
x= kjb+1 k j ∗ b + 1
移一下项:
akibkj=1 a ∗ k i − b ∗ k j = 1
是不是有点像扩建欧几里得的形式,
我们是要求 aki a ∗ k i 在n里面有多少个整数解,
看一下怎样统计。
设a的最小正整数解为 a1,b1 a 1 , b 1
那么a的通解就是a= pb1+a1 p ∗ b 1 + a 1
也就是说,我们要求有多少个p,
满足 pb1ki+a1b1 p ∗ b 1 ∗ k i + a 1 ∗ b 1 ≤n
这个就很简单了。

但是很显然,不能直接枚举i,j因为这样会算重。
而且题目的n这么小,很自然地想到容斥。
我们设A表示所有a方程的集合,对应着的,同样B表示所有b的方程。
这样每一个 ki k i 只会存在于要么A,要么B,要么两者都不。
这样枚举的复杂度是O( 3m 3 m )的,
而容斥系数就是 (1)|A|+|B| ( − 1 ) | A | + | B |
这里涉及了同余方程的合并,就是他们的lcm。

code

#include <queue>
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string.h>
#include <cmath>
#define ll long long
#define N 100003
#define M 103
#define P putchar
#define G getchar
using namespace std;
char ch;
void read(ll &n)
{
    n=0;
    ch=G();
    while((ch<'0' || ch>'9') && ch!='-')ch=G();
    ll w=1;
    if(ch=='-')w=-1,ch=G();
    while('0'<=ch && ch<='9')n=(n<<3)+(n<<1)+ch-'0',ch=G();
    n*=w;
}

int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
void write(ll x){if(x>9) write(x/10);P(x%10+'0');}

ll n,m,k[13],x,y,ans,S,g,d,_3[13],a,b,t;

ll exgcd(ll a,ll b,ll& x,ll& y)
{
    if(b==0)
    {
        x=1;y=0;return a;
    }
    else
    {
        ll d=exgcd(b,a%b,y,x);
        y=y-a/b*x;
        return d;
    }
}

ll calc(ll a,ll b)
{
    if(exgcd(a,b,x,y)!=1)return 0;
    x=(x%b+b)%b;
    if(n<x*a)return 0;
    return (n-x*a)/(a*b)+1;
}

void dfs(ll s1,ll s2,int x,int n1,int n2)
{
    if(s1>n || s2>n)return;
    if(x>m)
    {
        if(n1 && n2)ans=ans+calc(s1,s2)*((n1+n2&1)?-1:1);
        return;
    }
    dfs(s1*k[x]/exgcd(s1,k[x],a,b),s2,x+1,n1+1,n2);
    dfs(s1,s2*k[x]/exgcd(s2,k[x],a,b),x+1,n1,n2+1);
    dfs(s1,s2,x+1,n1,n2);
}

int main()
{
    freopen("sazetak.in","r",stdin);
    freopen("sazetak.out","w",stdout);

    _3[0]=1;
    for(int i=1;i<13;i++)
        _3[i]=_3[i-1]*3;
    read(n);read(m);
    for(int i=1;i<=m;i++)
        read(k[i]);
    k[++m]=n;

    dfs(1,1,1,0,0);

    printf("%lld",ans);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值