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。
- 先證充分性:如果有,那麼 的值以及 的值均可確定,兩式相減即爲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數組, 即爲答案。
- 時間複雜度: 。
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),滿足 。
- 上式可轉化爲 ,即 (因爲a2爲不定的係數,且可以爲負數,所以不必在意其正負性)。這個可以使用擴展歐幾里得求通解,詳見下文。
- 但是,對於 ,k1與k2、k3與k2都會計算,這時,就可能存在P值重複的情況。
- 應往容斥的方面想。
- 可以dfs出k1、k2的集合。設n1=|k1集合|,n2=|k2集合|,此時,我們得到了下列n1+n2個同餘方程:
- 考慮使用擴展中國剩餘定理將其合併。
- 但是,觀察到該方程組的特殊性,我們知道 ,並且 。因此,可以直接將上述方程組合併爲下列兩個方程:
- 令 。根據上述轉化,可得方程: 。
- 注意:這個s1、s2可能很大(當ki都兩兩互質時,lcm即爲乘積),但只要超過n就沒有意義。因爲我們是想用它們去覆蓋n個位置。
- 下面就要開始搞事了。
- 根據裴蜀定理,上述方程存在整數解,當且僅當 。那麼囿於1的約數只有1,所以實際上是當且僅當 。
- 然後,我們求解出一組x和y。但x、y可能爲負,我們要使它們變成最小的正整數。
- 顯然,若 成立,則 也成立。因此,x可以加上任意個s2,y可以減去任意個s1。
- 首先,我們將x增至最小的正整數,也即
x=(x%s2+s2)%s2;
此時,x*s1>0。我們又知道,x每次加s2,都有對應的唯一的y,因此,我們求的就是所有滿足0<x*s1<n的x的個數。 - x最多可以增加 次。而設若x*s1≤n,則它不增加也是滿足條件的。因此,貢獻爲 。
- 別忘了我們正在容斥。
- 對於每種方案,容斥係數應爲 。
- 證明詳見這位dalao的博客:https://blog.csdn.net/Cold_Chair/article/details/81566102。
- 然後這道題還有一個坑點。
- 假如我們知道 ,那也能知道x[n]。因爲M≥1,一定會有一種劃分,我們直接將那種劃分的所有和加起來,就是 。
- 而當所有劃分的K都不整除n時,程序就以爲我們不知道 。因此一開始需要打這麼一行代碼:
k[++m]=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);
}