【JZOJ5796】【2018提高组】模拟A组&省选 划分(容斥+扩展中国剩余定理+扩展欧几里得)

Problem

  有一个未知的序列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多少个元素的值。

Hint

对于20%的数据,3 <= N <= 2000,M<=3。
对于40%的数据,3 <= N <= 5*10^6。
对于100%的数据,3 <= N <= 10^9 , 1 <= M <= 10,2 <= K[i] < N。

Solution

40points:暴力

  • 首先,我们可以发现一个性质:若x[i]能被确定,充要条件为有区间的右端点在i和i-1。
  • 先证充分性:如果有,那么 j = 1 i x [ j ] 的值以及 j = 1 i 1 x [ j ] 的值均可确定,两式相减即为x[i]。
  • 再证必要性:如果某个区间覆盖了[l,r](l<i&&i<r),又有两个区间[l,i-1]和[i+1,l]把[l,r]分割了,那看上去也可以;但是既然有区间[i+1,l],肯定也有右端点在i的区间(它的前一个区间)。

  • 因此,我们可以设一个布尔数组p,p[i]表示是否存在右端点为i的区间。
  • 对于每个K[i],暴力枚举它的倍数,更新p数组。
  • 最后,扫一遍p数组, i = 1 n p [ i ] a n d p [ i 1 ] 即为答案。

  • 时间复杂度: O ( ( i = 1 m n k i ) + n )
Code
#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;

const int N=5e6+1;
int i,j,n,m,k[11],ans;
bool p[N];

int main()
{
    freopen("sazetak.in","r",stdin);
    freopen("sazetak.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,m)
    {
        scanf("%d",&k[i]);
        fo(j,1,n/k[i]) p[k[i]*j]=1;
    }
    p[n]=1;
    fo(i,1,n) ans+=(p[i]&&p[i-1]);
    printf("%d",ans);
}

100points:容斥+扩展中国剩余定理+扩展欧几里得

  • 上面所述的性质,装逼点写就是:若x[P]能求出,当且仅当存在一组(i,j),满足 P 0 ( m o d   k i ) , P 1 ( m o d   k j )
  • 上式可转化为 P = a 1 k i = a 2 k j + 1 ,即 a 1 k i + a 2 k j = 1 (因为a2为不定的系数,且可以为负数,所以不必在意其正负性)。这个可以使用扩展欧几里得求通解,详见下文。
  • 但是,对于 P 0 ( m o d   k 1 ) , P 1 ( m o d   k 2 ) , P 0 ( m o d   k 3 ) ,k1与k2、k3与k2都会计算,这时,就可能存在P值重复的情况。
  • 应往容斥的方面想。

  • 可以dfs出k1、k2的集合。设n1=|k1集合|,n2=|k2集合|,此时,我们得到了下列n1+n2个同余方程:
    P 0 ( m o d   k i 1 )
    P 0 ( m o d   k i 2 )
    . . .
    P 0 ( m o d   k i n 1 )
    P 1 ( m o d   k j 1 )
    P 1 ( m o d   k j 2 )
    . . .
    P 1 ( m o d   k j n 2 )
  • 考虑使用扩展中国剩余定理将其合并。
  • 但是,观察到该方程组的特殊性,我们知道 1 x n 1   k i x | P ,并且 1 x n 2   k j x | P 1 。因此,可以直接将上述方程组合并为下列两个方程:
    P 0 ( m o d   l c m ( k i 1 , k i 2 , . . . , k i n 1 ) )
    P 1 ( m o d   l c m ( k j 1 , k j 2 , . . . , k j n 2 ) )
  • s 1 = l c m ( k i 1 , k i 2 , . . . , k i n 1 ) , s 2 = l c m ( k j 1 , k j 2 , . . . , k j n 2 ) 。根据上述转化,可得方程: a 1 s 1 + a 2 s 2 = 1
  • 注意:这个s1、s2可能很大(当ki都两两互质时,lcm即为乘积),但只要超过n就没有意义。因为我们是想用它们去覆盖n个位置。
  • 下面就要开始搞事了。

  • 根据裴蜀定理,上述方程存在整数解,当且仅当 g c d ( s 1 , s 2 ) | 1 。那么囿于1的约数只有1,所以实际上是当且仅当 g c d ( s 1 , s 2 ) = 1
  • 然后,我们求解出一组x和y。但x、y可能为负,我们要使它们变成最小的正整数。
  • 显然,若 x s 1 + y s 2 = 1 成立,则 ( x + n s 2 ) s 1 + ( y n s 1 ) s 2 = 1 ( n Z ) 也成立。因此,x可以加上任意个s2,y可以减去任意个s1。
  • 首先,我们将x增至最小的正整数,也即x=(x%s2+s2)%s2;此时,x*s1>0。我们又知道,x每次加s2,都有对应的唯一的y,因此,我们求的就是所有满足0<x*s1<n的x的个数。
  • x最多可以增加 n s 1 次。而设若x*s1≤n,则它不增加也是满足条件的。因此,贡献为 n s 1 + [ x s 1 n ]


  • 然后这道题还有一个坑点。
  • 假如我们知道 i = 1 n 1 x [ i ] ,那也能知道x[n]。因为M≥1,一定会有一种划分,我们直接将那种划分的所有和加起来,就是 i = 1 n x [ i ]
  • 而当所有划分的K都不整除n时,程序就以为我们不知道 i = 1 n x [ i ] 。因此一开始需要打这么一行代码:k[++m]=n;

  • 时间复杂度: O ( m + 3 m l o g 2 n )

Code

#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;

int i,n,m,k[12];
ll d,x,y,ans;

ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
inline ll lcm(ll x,ll y){return x*y/gcd(x,y);}
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-=x*(a/b);}
}

void dfs(int t,ll s1,ll s2,ll f)
{
    if(max(s1,s2)>n) return;
    if(t>m)
    {
        if(s1==1||s2==1) return;
        exgcd(s1,s2,d,x,y);
        if(d<0) {x=-x; y=-y; d=-d;}
        if(d>1) return;
        x=(x%s2+s2)%s2;
        ll tmp=(1ll*n/s1-x)/s2+(s1*x<=n);
        ans+=f*tmp;
        return;
    }
    dfs(t+1,s1,s2,f);
    dfs(t+1,lcm(s1,k[t]),s2,-f);
    dfs(t+1,s1,lcm(s2,k[t]),-f);
}

int main()
{
    freopen("sazetak.in","r",stdin);
    freopen("sazetak.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,m) scanf("%d",&k[i]);
    k[++m]=n;
    dfs(1,1,1,1);
    printf("%lld",ans);
}

猜你喜欢

转载自blog.csdn.net/qq_36551189/article/details/81566823
今日推荐