【学习笔记】拉格朗日插值

[NOIP2020] 微信步数

首先把问题转化为将每一天中 还存活的起点数量 计入贡献。

然后考虑单独一维 j j j的情况。

注意到从第 2 2 2轮开始,每次死的起点数是固定的。设最后 i i i步新死的起点数是 f j f_j fj(事实上 f j f_j fj和进行的轮数无关,只和维度 j j j有关),那么事实上这是个一次函数。

首先第 1 1 1轮单独算。然后第 T + 2 T+2 T+2轮贡献 ∏ j = 1 k ( w j − r j + l j − T × v − f j ) \prod_{j=1}^k(w_j-r_j+l_j-T\times v-f_j) j=1k(wjrj+ljT×vfj) k k k次多项式,求其前缀和为 k + 1 k+1 k+1次多项式。

基于以上观察,我们可以拉插求解,复杂度 O ( n k 2 ) O(nk^2) O(nk2)

总结:问题的关键在于想到周期性的规律,然后大力多项式。( y l y yly yly说这是最基础的题了。。。)

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
using namespace std;
const int mod=1e9+7;
int n,K;
ll w[15],v[15],v2[15],f[15];
int c[500005],d[500005];
ll l[15],r[15],l2[15],r2[15],inv[15];
ll res;
ll fpow(ll x,ll y){
	ll z(1);for(;y;y>>=1){
		if(y&1)z=z*x%mod;x=x*x%mod;
	}return z;
}
ll LG(int y) {
	if(y<=K+1) {
		ll res=0;
		for(int i=0;i<=y;i++) {
			ll mul=1;
			for(int j=1;j<=K;j++){
				mul=mul*(w[j]-r[j]+l[j]-i*abs(v[j])-f[j])%mod;
			}res=(res+mul)%mod;
		}return res;
	}
	ll res=0,tmp=0;
	for(int i=0;i<=K+1;i++) {
		ll mul=1;
		for(int j=1;j<=K;j++) {
			mul=mul*(w[j]-r[j]+l[j]-i*abs(v[j])-f[j])%mod;
		}tmp=(tmp+mul)%mod;mul=tmp;
		for(int j=0;j<=K+1;j++) {
			if(i!=j) {
				if(i>=j)mul=mul*(y-j)%mod*inv[i-j]%mod;
				else mul=-mul*(y-j)%mod*inv[j-i]%mod;
			}
		}res=(res+mul)%mod;
	}return res;
} 
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>K;for(int i=1;i<=K+1;i++)inv[i]=fpow(i,mod-2);
	for(int i=1;i<=K;i++)cin>>w[i];
	for(int i=1;i<=n;i++) {
		cin>>c[i]>>d[i],v[c[i]]+=d[i];
		l[c[i]]=min(l[c[i]],v[c[i]]),r[c[i]]=max(r[c[i]],v[c[i]]);
	}
	int ok=0;for(int i=1;i<=K;i++)if(v[i])ok=1;
	if(!ok) {cout<< -1;return 0;}
	res=1;for(int i=1;i<=K;i++)res=res*w[i]%mod;
	for(int i=1;i<=n;i++) {
        v2[c[i]]+=d[i];
		l2[c[i]]=min(l2[c[i]],v2[c[i]]),r2[c[i]]=max(r2[c[i]],v2[c[i]]);
		int ok=1;ll mx=0x3f3f3f3f;ll mul=1;for(int j=1;j<=K;j++){
            if(r2[j]-l2[j]>=w[j]){ok=0;break;}mul=mul*(w[j]-r2[j]+l2[j])%mod;
        }if(!ok)continue;res=(res+mul)%mod;
        for(int j=1;j<=K;j++) {
            if(!v[j])continue;
            if(v[j]>0)f[j]=max(0ll,r2[j]+v[j]-r[j]);
            else f[j]=max(0ll,-l2[j]-v[j]+l[j]);
        }
		for(int j=1;j<=K;j++){
            if(r[j]-l[j]+f[j]>=w[j]){ok=0;break;}
            if(v[j])mx=min(mx,(w[j]-r[j]+l[j]-f[j])/abs(v[j]));
        }if(!ok)continue;res=(res+LG(mx))%mod;
	} cout<<(res+mod)%mod;
}

[APIO2016] 划艇

拉插做法比较复杂。滚回去学多项式基础了

考虑简单 d p dp dp。然后是关键的观察:

1.1 1.1 1.1 [ 0 , m ] [0,m] [0,m]中选 n n n个数,满足非零数单调递增的方案数是 ( n + m n ) \binom{n+m}{n} (nn+m)

证明:考虑枚举 0 0 0的个数,答案是 ∑ i = 0 n ( n i ) ( m n − i ) \sum_{i=0}^n\binom{n}{i}\binom{m}{n-i} i=0n(in)(nim)。容易发现它等价于从 n + m n+m n+m个数中选取 n n n个数,也就是 ( n + m n ) \binom{n+m}{n} (nn+m)

如果钦定第 n n n个数非零的话,答案是 ( n + m n ) − ( n + m − 1 n − 1 ) \binom{n+m}{n}-\binom{n+m-1}{n-1} (nn+m)(n1n+m1)

f i , j f_{i,j} fi,j表示第 i i i个学校派出的划艇数目在第 j j j段的方案数。显然维护前缀和转移。复杂度 O ( n 3 ) O(n^3) O(n3)

其实做到这一步可以想到拉插,形式化的说,每一段 [ a i , a i + 1 ) [a_i,a_{i+1}) [ai,ai+1)对应一个 n n n次多项式。

怎么观察到这一步的呢。注意看每一段里面具体的 j j j只影响组合数的值,前面公共的那一坨可以看成常数,而组合数又是与 j j j有关的多项式,于是可以拉插。自然一段内的前缀和也是 n + 1 n+1 n+1次多项式。

具体实现看代码。

我信仰什么,我便实现哪种方法。

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
using namespace std;
const int mod=1e9+7;
int n,a[505],b[505],lsh[1005],cnt;
ll f[505][1005],inv[505],res;
ll fpow(ll x,ll y) {
 ll z(1);
 for(;y;y>>=1) {
  if(y&1)z=z*x%mod;
  x=x*x%mod;
 }return z;
}
int main() {
 ios::sync_with_stdio(false);
 cin>>n;for(int i=1;i<=n;i++) inv[i]=fpow(i,mod-2);
 for(int i=1;i<=n;i++) {
  cin>>a[i]>>b[i],b[i]++;
  lsh[++cnt]=a[i],lsh[++cnt]=b[i];
 }sort(lsh+1,lsh+1+cnt),cnt=unique(lsh+1,lsh+1+cnt)-lsh-1;
 for(int i=1;i<=n;i++) {
  a[i]=lower_bound(lsh+1,lsh+1+cnt,a[i])-lsh;
  b[i]=lower_bound(lsh+1,lsh+1+cnt,b[i])-lsh-1;
 }
 for(int i=0;i<=cnt;i++) f[0][i]=1;
 for(int i=1;i<=n;i++) {
  for(int j=a[i];j<=b[i];j++) {
   ll L=1,mul=lsh[j+1]-lsh[j];
   for(int k=i-1;k>=0;k--) {
    f[i][j]=(f[i][j]+mul*f[k][j-1]%mod)%mod;
    if(a[k]<=j&&j<=b[k]) {
     mul=mul*(lsh[j+1]-lsh[j]+L)%mod*inv[L+1]%mod,L++;
    }
   }  }
  for(int j=a[i];j<=cnt;j++) {
   f[i][j]=(f[i][j]+f[i][j-1])%mod;
  }
  res=(res+f[i][cnt])%mod; }cout<<res;
}

大常数拉插。好逊啊

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
using namespace std;
const int mod=1e9+7;
int n,a[505],b[505],lsh[1005],cnt;
ll fac[505],inv[505],res;
ll g[1005][505],val[1005],sum[1005];
ll iv[1005][505];
ll fpow(ll x,ll y) {
 ll z(1);
 for(;y;y>>=1) {
  if(y&1)z=z*x%mod;
  x=x*x%mod;
 }return z;
}
void add(ll &x,ll y) {x=(x+y)%mod;}
ll LG(int x,ll y) {
 if(lsh[x+1]-lsh[x]<=n+2) {
  ll tmp=0;for(int j=0;j<lsh[x+1]-lsh[x];j++)add(tmp,g[x][j]);
  return tmp;
 }ll tmp=0,res=0,mul=1;
 for(int i=0;i<=n+1;i++)mul=mul*(y-i)%mod;
 for(int i=0;i<=n+1;i++) {
  add(tmp,g[x][i]);
  if(n+1-i&1) {
   res=(res-tmp*mul%mod*iv[x][i]%mod*inv[i]%mod*inv[n+1-i]%mod)%mod;
  }
  else {
   res=(res+tmp*mul%mod*iv[x][i]%mod*inv[i]%mod*inv[n+1-i]%mod)%mod;
  }
 }return (res+mod)%mod;
}
int main() {
// freopen("fuck.in","r",stdin);
 ios::sync_with_stdio(false);
 cin>>n;fac[0]=1;for(int i=1;i<=n+1;i++)fac[i]=fac[i-1]*i%mod;
 inv[n+1]=fpow(fac[n+1],mod-2);for(int i=n+1;i>=1;i--)inv[i-1]=inv[i]*i%mod;
 for(int i=1;i<=n;i++) {
  cin>>a[i]>>b[i],b[i]++;
  lsh[++cnt]=a[i],lsh[++cnt]=b[i];
 }sort(lsh+1,lsh+1+cnt),cnt=unique(lsh+1,lsh+1+cnt)-lsh-1;
 for(int i=1;i<=n;i++) {
  a[i]=lower_bound(lsh+1,lsh+1+cnt,a[i])-lsh;
  b[i]=lower_bound(lsh+1,lsh+1+cnt,b[i])-lsh;
 }
 for(int i=1;i<cnt;i++) {
     for(int j=0;j<=n+1;j++) {
      iv[i][j]=fpow(lsh[i+1]-lsh[i]-1-j,mod-2);
  }
 }
 g[0][0]=1;for(int i=0;i<=cnt;i++)sum[i]=1;
 for(int i=1;i<=n;i++) {
  for(int j=b[i];j<=cnt;j++) val[j]=sum[j]-sum[j-1];
  for(int j=a[i];j<b[i];j++) {
   add(g[j][0],sum[j-1]);
   for(int k=1;k<=n+1;k++) add(g[j][k],g[j][k-1]);
   val[j]=LG(j,lsh[j+1]-lsh[j]-1);
  }for(int j=a[i];j<b[i];j++)sum[j]=(sum[j-1]+val[j])%mod;
  for(int j=b[i];j<=cnt;j++)sum[j]=(sum[j-1]+val[j])%mod;
 }cout<<(sum[cnt]+mod-1)%mod;
}

[省选联考 2022] 填树

这题挺烦的。

a i a_i ai, a i − K a_i-K aiK, b i b_i bi, b i − K b_i-K biK塞进去离散化,很容易看出这是一个分段多项式。对每一段分别求答案,实现时还是对前缀和插值。

d p dp dp求链的时候在 L C A LCA LCA处统计答案。第一个问题的多项式次数是 n + 1 n+1 n+1,第二个问题的多项式次数是 n + 2 n+2 n+2

复杂度 O ( n 3 ) O(n^3) O(n3)

细节贼多。恶心死我了。

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
using namespace std;
const int mod=1e9+7;
int n,K,ct;
ll l[805],r[805],lsh[805],sum,inv2;
ll inv[805],fac[805],dp[805],dp2[805];
vector<int>g[805];
ll fpow(ll x,ll y){
 ll z(1);for(;y;y>>=1){
  if(y&1)z=z*x%mod;x=x*x%mod;
 }return z;
}
void init(int n){
 fac[0]=1,inv2=fpow(2,mod-2);for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
 inv[n]=fpow(fac[n],mod-2);for(int i=n;i>=1;i--)inv[i-1]=inv[i]*i%mod;
}
void dfs(int u,ll x,ll K,int topf){
 ll L=max(x,l[u]),R=min(x+K,r[u]-1);
 if(L<=R)dp[u]=R-L+1,sum=(sum+R-L+1)%mod;
 for(auto v:g[u]){
  if(v!=topf){
   dfs(v,x,K,u);
   if(L<=R){
    sum=(sum+dp[u]*dp[v]%mod)%mod;
    dp[u]=(dp[u]+(R-L+1)*dp[v]%mod)%mod;
   }
  }
 }
}
ll Sum(ll x){
 return x*(x+1)%mod*inv2%mod;
} 
void dfs2(int u,ll x,ll K,int topf){
 ll L=max(x,l[u]),R=min(x+K,r[u]-1);
 if(L<=R)dp[u]=R-L+1,dp2[u]=Sum(R)-Sum(L-1),sum=(sum+Sum(R)-Sum(L-1))%mod;
 for(auto v:g[u]){
  if(v!=topf){
   dfs2(v,x,K,u);
   if(L<=R){
    sum=(sum+dp2[u]*dp[v]%mod+dp2[v]*dp[u]%mod)%mod;
    dp[u]=(dp[u]+(R-L+1)*dp[v]%mod)%mod;
    dp2[u]=(dp2[u]+dp2[v]*(R-L+1)%mod+dp[v]*(Sum(R)-Sum(L-1))%mod)%mod;
   }
  }
 }
}
ll calc(ll x,ll K){
 memset(dp,0,sizeof dp),sum=0,dfs(1,x,K,0);
 return sum;
}
ll calc2(ll x,ll K){
 memset(dp,0,sizeof dp),memset(dp2,0,sizeof dp2),sum=0,dfs2(1,x,K,0); '<<sum<<endl;
 return sum;
}
ll solve(ll K){
 ll res=0;
 for(int i=1;i<ct;i++){
  int cnt=lsh[i+1]-lsh[i];
  if(cnt<=n+2){
      for(int j=0;j<cnt;j++)res=(res+calc(lsh[i]+j,K))%mod;
  }
  else{
   ll tmp=0,mul=1;for(int j=0;j<=n+1;j++)mul=mul*(cnt-1-j)%mod;
   for(int j=0;j<=n+1;j++){
    tmp=(tmp+calc(lsh[i]+j,K))%mod;
    if(n+1-j&1)res=(res-tmp*mul%mod*fpow(cnt-1-j,mod-2)%mod*inv[n+1-j]%mod*inv[j]%mod)%mod;
    else res=(res+tmp*mul%mod*fpow(cnt-1-j,mod-2)%mod*inv[n+1-j]%mod*inv[j]%mod)%mod;
   }
  }
 }return (res+mod)%mod;
}
ll solve2(ll K){
 ll res=0;
 for(int i=1;i<ct;i++){
  int cnt=lsh[i+1]-lsh[i];
  if(cnt<=n+3){
   for(int j=0;j<cnt;j++)res=(res+calc2(lsh[i]+j,K))%mod;
  }
  else {
   ll tmp=0,mul=1;for(int j=0;j<=n+2;j++)mul=mul*(cnt-1-j)%mod;
   for(int j=0;j<=n+2;j++){
    tmp=(tmp+calc2(lsh[i]+j,K))%mod;
    if(n+2-j&1)res=(res-tmp*mul%mod*fpow(cnt-1-j,mod-2)%mod*inv[n+2-j]%mod*inv[j]%mod)%mod;
    else res=(res+tmp*mul%mod*fpow(cnt-1-j,mod-2)%mod*inv[n+2-j]%mod*inv[j]%mod)%mod;
   }
  }
 }return (res+mod)%mod;
} 
signed main(){
 freopen("tree.in","r",stdin);
 freopen("tree.out","w",stdout);
 ios::sync_with_stdio(false);
 cin.tie(0),cout.tie(0);
 cin>>n>>K,init(2*n);for(int i=1;i<=n;i++){
  cin>>l[i]>>r[i],r[i]++,lsh[++ct]=l[i],lsh[++ct]=r[i];
  if(l[i]>K)lsh[++ct]=l[i]-K;if(r[i]>K)lsh[++ct]=r[i]-K;
 }lsh[++ct]=1;sort(lsh+1,lsh+1+ct),ct=unique(lsh+1,lsh+1+ct)-lsh-1;
 for(int i=1;i<n;i++){
  int u,v;cin>>u>>v,g[u].pb(v),g[v].pb(u);
 }
 cout<<(solve(K)-solve(K-1)+calc(1,K-1)+mod)%mod<<endl;
 cout<<((solve2(K)-solve2(K-1)+calc2(1,K-1)+mod)%mod+mod)%mod; 
} 

[NOI2019] 机器人

玄学 d p dp dp优化。。。

不难看出这是一个区间 d p dp dp/kk

f l , r , M f_{l,r,M} fl,r,M表示区间 [ l , r ] [l,r] [l,r]中每个位置高度均不大于 M M M的方案数。

注意到从区间最大高度出发能移动到整个区间,并且将整个区间分成了两个独立的子问题,那么 f l , r , M = ∑ k = 1 M f l , m i d − 1 , k × f m i d + 1 , r , k − 1 [ a m i d ≤ k ≤ b m i d ] f_{l,r,M}=\sum_{k=1}^Mf_{l,mid-1,k}\times f_{mid+1,r,k-1}[a_{mid}\le k\le b_{mid}] fl,r,M=k=1Mfl,mid1,k×fmid+1,r,k1[amidkbmid]。答案是 f 1 , n , max ⁡ b i f_{1,n,\max b_i} f1,n,maxbi。时间复杂度 O ( n 2 A ) O(n^2A) O(n2A)

注意到答案是分段多项式。因此对于一段区间只需维护 n n n项取值。复杂度 O ( n 4 ) O(n^4) O(n4)

注意到有用区间只有大概 O ( n ) O(n) O(n)个,因此总复杂度 O ( n 3 ) O(n^3) O(n3)

代码比较恶心。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值