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

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],他们能确定多少个 x x

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

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

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

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

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

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

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

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

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

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

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

对于一个x, x mod a[i]=0 x   m o d   a [ i ] = 0 的a[i]属于A集合, x mod a[j]=1 x   m o d   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);
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值