【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 ] | ( x 1 )

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

x = p a [ i ] ( x <= n )

p a [ i ] = 1 ( m o d   a [ j ] )
p = a [ i ] 1 ( m o d   a [ j ] )

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

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

= n a [ i ] a [ j ] + [ n a [ i ]   m o d   a [ j ] >= p ]

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

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

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

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

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

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

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

则x会被计算:
i = 1 | A | C | A | i j = 1 | B | C | B | j ( 1 ) i + j
= i = 1 | A | C | A | i ( 1 ) i j = 1 | B | C | B | j ( 1 ) j
= ( ( 1 1 ) | A | 1 ) ( ( 1 1 ) | B | 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);
}

猜你喜欢

转载自blog.csdn.net/Cold_Chair/article/details/81566102