【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=1ix[j] 的值以及j=1i1x[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=1np[i]andp[i1] 即爲答案。

  • 時間複雜度:O((i=1mnki)+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),滿足P0(mod ki),P1(mod kj)
  • 上式可轉化爲P=a1ki=a2kj+1 ,即a1ki+a2kj=1 (因爲a2爲不定的係數,且可以爲負數,所以不必在意其正負性)。這個可以使用擴展歐幾里得求通解,詳見下文。
  • 但是,對於P0(mod k1),P1(mod k2),P0(mod k3) ,k1與k2、k3與k2都會計算,這時,就可能存在P值重複的情況。
  • 應往容斥的方面想。

  • 可以dfs出k1、k2的集合。設n1=|k1集合|,n2=|k2集合|,此時,我們得到了下列n1+n2個同餘方程:
    P0(mod ki1)
    P0(mod ki2)
    ...
    P0(mod kin1)
    P1(mod kj1)
    P1(mod kj2)
    ...
    P1(mod kjn2)
  • 考慮使用擴展中國剩餘定理將其合併。
  • 但是,觀察到該方程組的特殊性,我們知道1xn1 kix|P ,並且1xn2 kjx|P1 。因此,可以直接將上述方程組合併爲下列兩個方程:
    P0(mod lcm(ki1,ki2,...,kin1))
    P1(mod lcm(kj1,kj2,...,kjn2))
  • s1=lcm(ki1,ki2,...,kin1),s2=lcm(kj1,kj2,...,kjn2) 。根據上述轉化,可得方程:a1s1+a2s2=1
  • 注意:這個s1、s2可能很大(當ki都兩兩互質時,lcm即爲乘積),但只要超過n就沒有意義。因爲我們是想用它們去覆蓋n個位置。
  • 下面就要開始搞事了。

  • 根據裴蜀定理,上述方程存在整數解,當且僅當gcd(s1,s2)|1 。那麼囿於1的約數只有1,所以實際上是當且僅當gcd(s1,s2)=1
  • 然後,我們求解出一組x和y。但x、y可能爲負,我們要使它們變成最小的正整數。
  • 顯然,若xs1+ys2=1 成立,則(x+ns2)s1+(yns1)s2=1(nZ) 也成立。因此,x可以加上任意個s2,y可以減去任意個s1。
  • 首先,我們將x增至最小的正整數,也即x=(x%s2+s2)%s2;此時,x*s1>0。我們又知道,x每次加s2,都有對應的唯一的y,因此,我們求的就是所有滿足0<x*s1<n的x的個數。
  • x最多可以增加ns1 次。而設若x*s1≤n,則它不增加也是滿足條件的。因此,貢獻爲ns1+[xs1n]


  • 然後這道題還有一個坑點。
  • 假如我們知道i=1n1x[i] ,那也能知道x[n]。因爲M≥1,一定會有一種劃分,我們直接將那種劃分的所有和加起來,就是i=1nx[i]
  • 而當所有劃分的K都不整除n時,程序就以爲我們不知道i=1nx[i] 。因此一開始需要打這麼一行代碼:k[++m]=n;

  • 時間複雜度:O(m+3mlog2n)

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);
}
發佈了114 篇原創文章 · 獲贊 52 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章