【2018提高组】模拟A组&省选 划分(二项式反演优化容斥)

本文探讨了在一个未知序列x的多种K-划分序列已知的情况下,如何确定原序列中元素值的问题。通过数学推导,给出了确定元素数量的具体算法,并使用容斥原理解决了重复计算的问题。

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多少个元素的值。

3 <= N <= 10^9 , 1 <= M <= 10,2 <= K[i] < N。

题解:

考虑x什么时候是确定的?

当且仅当有:
a[i]|x,a[j]|(x1)a[i]|x,a[j]|(x−1)

考虑有a[i]和a[j],他们能确定多少个xx

x=pa[i](x<=n)

pa[i]=1(mod a[j])p∗a[i]=1(mod a[j])
p=a[i]1(mod a[j])p=a[i]−1(mod a[j])

显然gcd(a[i],a[j])=1gcd(a[i],a[j])=1才解。

a[i]1(mod a[j])a[i]−1(mod a[j])可以用欧拉定理或者扩展欧几里得算法解决。

=na[i]a[j]+[na[i] mod a[j]>=p]答案个数=⌊⌊na[i]⌋a[j]⌋+[⌊na[i]⌋ mod a[j]>=p]

直接枚举所有的a[i]、a[j]算答案显然会有重。

那么我们用容斥搞掉重复,假设有多组限制,则分开求lcm,再求答案即可。

复杂度为O(2m(m1))O(2m∗(m−1)),加剪枝可以拿90分。

假设分成了A、B两个集合,这两个集合显然会有重复元素, 但是我们求lcm不需要考虑重复元素。

因此直接枚举A、B集合的不同元素,容斥系数为(1)|A|+|B|(−1)|A|+|B|

下面利用二项式反演来证明这个结论:

对于一个x,x mod a[i]=0x mod a[i]=0的a[i]属于A集合,x mod a[j]=1x mod a[j]=1的a[j]属于B集合。

则x会被计算:
|A|i=1Ci|A||B|j=1Cj|B|(1)i+j∑i=1|A|C|A|i∑j=1|B|C|B|j(−1)i+j
=|A|i=1Ci|A|(1)i|B|j=1Cj|B|(1)j=∑i=1|A|C|A|i∗(−1)i∑j=1|B|C|B|j∗(−1)j
=((11)|A|1)((11)|B|1)=((1−1)|A|−1)∗((1−1)|B|−1)
=1=1

Code:

#include<cstdio>
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define max(a, b) ((a) > (b) ? (a) : (b)) 
using namespace std;

const int N = 1e5;

int n, m, a[11], ni[11];

int gcd(int x, int y) {
    return !y ? x : gcd(y, x % y);
}

int x, y;

void exgcd(int a, int b) {
    if(b == 0) {x = a; y = 0; return;}
    exgcd(b, a % b);
    int xx = x, yy = y;
    x = yy; y = xx - (a / b) * yy;
}

ll ans;

void dg(int i, ll y, ll z, int fu) {
    if(y > n || z > n) return;
    if(i > m) {
        if(y == 1 || z == 1) return;
        if(gcd(y, z) > 1) return;
        exgcd(y, -z); x = ((x % z) + z) % z;
        ans += fu * (n / y / z + (n / y % z >= x));
        return;
    }
    dg(i + 1, y, z, fu);
    dg(i + 1, y * a[i] / gcd(y, a[i]), z, -fu);
    dg(i + 1, y, z * a[i] / gcd(z, a[i]), -fu);
}

int main() {
    freopen("sazetak.in", "r", stdin);
    freopen("sazetak.out", "w", stdout);
    int bz1 = 1;
    scanf("%d %d", &n, &m);
    fo(i, 1, m) {
        scanf("%d", &a[i]);
        bz1 &= a[i] == 1;
    }
    if(bz1) {printf("%d", n); return 0;}
    n --;
    dg(1, 1, 1, 1);
    n ++;
    fo(i, 1, m) bz1 |= (n - 1) % a[i] == 0;
    if(bz1) ans ++;
    printf("%d", ans);
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值