NOIp 2020 微信步数 题解

题目传送门

题目大意: 有一个 k k k 维空间,你要从每一个位置出发走一条循环路线,直到走出边界为止,求走过的总步数。

题解

转化一下,每天走过的总步数,等于每个时刻能走的天数之和。

n n n 步看成一轮,那么每轮内在一维上走过的一定是个区间。

设 一轮内,走了第 i i i 步后,在第 j j j 维走过的这个区间为 [ x j + l i , j , x j + r i , j ] [x_j+l_{i,j},x_j+r_{i,j}] [xj+li,j,xj+ri,j],那么在走到第 i i i 步还没有走出边界的点,就需要满足 1 − l i , j ≤ x j ≤ w j − r i , j 1-l_{i,j}\leq x_j\leq w_j-r_{i,j} 1li,jxjwjri,j

设 一轮后,第 j j j 维的位移为 d j d_j dj。除了第一轮和最后一轮,走过一轮后,第 j j j 维一定有恰好 d j d_j dj 个位置走出边界,那么第 2 + t ( t ≥ 0 ) 2+t(t\geq 0) 2+t(t0) 轮的第 i i i 步时,第 j j j 维能用的位置就有 w j − max ⁡ ( r n , j , r i , j + d j ) + m i n ( l n , j , l i , j + d j ) − ∣ d j ∣ × t w_j-\max(r_{n,j},r_{i,j}+d_j)+min(l_{n,j},l_{i,j}+d_j)-|d_j|\times t wjmax(rn,j,ri,j+dj)+min(ln,j,li,j+dj)dj×t 个。

乘起来,就得到了第 t t t 轮,第 i i i 步时还没有走出边界的起点个数。发现这是个关于 t t t k k k 次多项式,记为 f i ( t ) = ∑ j = 0 k a j t j f_i(t)=\sum_{j=0}^k a_jt^j fi(t)=j=0kajtj,不难得出 t t t 的上界,记为 t m a x t_{max} tmax,那么贡献就是 ∑ t = 0 t m a x f i ( t ) = ∑ t = 0 t m a x ∑ j = 0 k a j t j = ∑ j = 0 k a j ∑ t = 0 t m a x t j \sum_{t=0}^{t_{max}}f_i(t)=\sum_{t=0}^{t_{max}}\sum_{j=0}^ka_jt^j=\sum_{j=0}^ka_j\sum_{t=0}^{t_{max}}t^j t=0tmaxfi(t)=t=0tmaxj=0kajtj=j=0kajt=0tmaxtj

后面是个自然数幂和,用拉格朗日插值搞搞就行。那么每次需要 k 2 k^2 k2 暴力卷出 f i ( t ) f_i(t) fi(t),时间复杂度就是 O ( n k 2 ) O(nk^2) O(nk2)

以及第 1 1 1 轮和第 t m a x + 1 t_{max}+1 tmax+1 轮要单独计算,用那个将每一维可用起点数乘起来的做法算即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define mod 1000000007

int n,k,w[11],ans=0;
int d[11];
int l[500010][11],r[500010][11];
void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
void dec(int &x,int y){x=(x-y<0?x-y+mod:x-y);}
struct Poly{
	int a[11],t;
	Poly(){t=0;memset(a,0,sizeof(a));}
	void clear(){t=0;memset(a,0,sizeof(a));}
	void operator *=(Poly &B){
		Poly re;re.t=t+B.t;
		for(int i=0;i<=t;i++)
			for(int j=0;j<=B.t;j++)
				add(re.a[i+j],1ll*a[i]*B.a[j]%mod);
		*this=re;
	}
}s[11];
int t_max=1e9;
int calc_round_1(){//暴力求解第一轮
	int re=0;
	for(int i=1;i<=n;i++){
		int prod=1;
		for(int j=1;j<=k;j++)
			if(w[j]-r[i][j]+l[i][j]>0)
				prod=1ll*prod*(w[j]-r[i][j]+l[i][j])%mod;
			else {prod=0;break;}
		add(re,prod);
	}
	return re;
}
int calc_round_t_max_and_1(){//暴力求解最后一轮
	int re=0;
	for(int i=1;i<=n;i++){
		int prod=1;
		for(int j=1;j<=k;j++){
			int p=w[j]-max(r[n][j],r[i][j]+d[j])+min(l[n][j],l[i][j]+d[j])-(t_max+1)*abs(d[j]);
			if(p>0)prod=1ll*prod*p%mod;
			else {prod=0;break;}
		}
		add(re,prod);
	}
	return re;
}
int ksm(int x,int y){int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
struct Lagrange{//自然数幂和板子
	int fac[20],inv_fac[20];
	Lagrange(){}
	void init(){
		fac[0]=inv_fac[0]=1;
		for(int i=1;i<=15;i++)fac[i]=1ll*fac[i-1]*i%mod;
		inv_fac[15]=ksm(fac[15],mod-2);
		for(int i=14;i>=1;i--)inv_fac[i]=1ll*inv_fac[i+1]*(i+1)%mod;
	}
	int pre[20],suf[20];
	int calc(int k,int x){
		if(!k)return x+1;
		int re=0,sum=0;
		if(x<=k+2){
			for(int i=0;i<=x;i++)add(re,ksm(i,k));
			return re;
		}
		pre[0]=1;for(int i=1;i<=k+2;i++)pre[i]=1ll*pre[i-1]*(x-i)%mod;
		suf[k+3]=1;for(int i=k+2;i>=1;i--)suf[i]=1ll*suf[i+1]*(x-i)%mod;
		for(int i=1;i<=k+2;i++){
			add(sum,ksm(i,k));
			(k+2-i&1?dec:add)(re,1ll*sum*pre[i-1]%mod*suf[i+1]%mod
							*inv_fac[i-1]%mod*inv_fac[k+2-i]%mod);
		}
		return re;
	}
}Lag;
int cd[20];

int main()
{
	scanf("%d %d",&n,&k);ans=1;
	for(int i=1;i<=k;i++)
		scanf("%d",&w[i]),ans=1ll*ans*w[i]%mod;
	for(int i=1,x,y;i<=n;i++){
		scanf("%d %d",&x,&y);
		d[x]+=y;
		for(int j=1;j<=k;j++)
			l[i][j]=min(l[i-1][j],d[j]),
			r[i][j]=max(r[i-1][j],d[j]);
	}
	bool tf=false;//判无解
	for(int i=1;i<=k;i++)
		if(l[n][i]+r[n][i]>=w[i]||d[i]!=0)tf=true;
	if(!tf)return puts("-1"),0;
	
	for(int i=1;i<=k;i++)//求t_max
		if(w[i]-r[n][i]+l[n][i]<=0){t_max=-2;break;}
		else if(d[i]!=0)t_max=min(t_max,(w[i]-r[n][i]+l[n][i])/abs(d[i])-1);
	
	add(ans,calc_round_1());
	if(t_max>-2)add(ans,calc_round_t_max_and_1());
	if(t_max>=0){
		Lag.init();//预处理自然数幂和
		for(int i=0;i<=k;i++)
			cd[i]=Lag.calc(i,t_max);
		for(int i=1;i<=n;i++){
			for(int j=1;j<=k;j++){
				s[j].clear();//计算出所有多项式,暴力乘起来
				s[j].t=d[j]!=0;
				s[j].a[0]=w[j]-max(r[n][j],r[i][j]+d[j])+min(l[n][j],l[i][j]+d[j]);
				s[j].a[1]=(mod-abs(d[j]))%mod;
				if(j>1)s[j]*=s[j-1];
			}
			for(int j=0;j<=k;j++)
				add(ans,1ll*s[k].a[j]*cd[j]%mod);
		}
	}
	printf("%d",ans);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值