[NOIP2020] 微信步数 题解

[NOIP2020] 微信步数 题解


知识点

模拟,(连续横坐标下的)Lagrange 插值

分析

我们肯定是要把所有的起点放到一起算,然后就形成了一个 \(k\) 维长方体,容易发现各个维度毫无关联,可以分开计算。

无解

要是第一轮存在没有出界的点,并且一轮后仍在原地,那么它就会无解。

代码

bool No_Solution() {
	static int l[M],r[M];
	RCL(l,0,l,1),RCL(r,0,r,1);
	FOR(i,1,n)e[c[i]]+=d[i],tomax(r[c[i]],e[c[i]]),tomin(l[c[i]],e[c[i]]);
	FOR(i,1,m)if(r[i]-l[i]>=w[i])return 0;
	FOR(i,1,m)if(e[i])return 0;
	return 1;
}

暴力

只要能跑,就一直跑下去,每跑一次,就算一下还剩多少点。

时间复杂度:\(O(m\sum{w_i})\)

代码

namespace Subtask1 {
	int l[M],r[M];
	bool Check() {
		FOR(i,1,m)if(w[i]>1e5)return 0;
		return 1;
	}
	int Cmain() {
		ans=1;
		FOR(i,1,m)tomul(ans,w[i]);
		while(true)FOR(i,1,n) {
			e[c[i]]+=d[i],tomin(l[c[i]],e[c[i]]),tomax(r[c[i]],e[c[i]]);
			int s(1);
			FOR(j,1,m) {
				if(r[j]-l[j]>=w[j])return printf("%d\n",ans),0;
				tomul(s,w[j]-(r[j]-l[j]));
			}
			toadd(ans,s);
		}
		return 0;
	}
}

正解

我们可以发现,上述暴力中每轮 \(n\) 次各个维度的移动方向与次数是相同的,所以当第一轮 \(n\) 次结束后,后面的所有轮中,点出界的情况都是一样的,总数、次序都相同。

那么我们可以先跑出第一轮,然后缩小范围,枚举每轮中的第 \(i\) 次,然后进行求解,再累加入答案。

处理第一轮的次数并缩小范围:

\(e_j\) 用来记录当前第 \(j\) 维移动情况,\(l_{i,j},r_{i,j}\) 分别表示第一轮中第 \(i\) 次第 \(j\) 维最大、最小分别到过哪里。

FOR(i,1,n) {
	e[c[i]]+=d[i];
	FOR(j,1,m)l[i][j]=l[i-1][j],r[i][j]=r[i-1][j];
	tomin(l[i][c[i]],e[c[i]]),tomax(r[i][c[i]],e[c[i]]);
}
FOR(j,1,m)a[j]=w[j]-(r[n][j]-l[n][j]);//第一轮后还剩多少
FOR(i,0,n) {
	int s(1);
	FOR(j,1,m)tomul(s,max(0,w[j]-(r[i][j]-l[i][j])));
	toadd(ans,s);
}//第一轮
FOR(i,1,n)FOR(j,1,m)tomin(l[i][j]+=e[j]-l[n][j],0),tomax(r[i][j]+=e[j]-r[n][j],0);
FOR(j,1,m)b[j]=r[n][j]-l[n][j];

那么我们可以处理出 \(a_j,b_j\) 分别表示第 \(j\) 维在第一轮后还剩多少点,每轮这一维会减少多少,而 \(l_{i,j},r_{i,j}\) 则被转化成到之后每一轮的第 \(i\) 步时,第 \(j\) 维在正负方向分别会减少多少点。

那么第 \(t\) 轮(从原本的第二轮重新开始计算,从 \(0\) 开始)第 \(i\) 次操作时,第 \(j\) 维还剩 \(a_j - t b_j -(r_{i,j} - l_{i,j})\)

现在可以开始枚举操作,然后判断它会进行几轮,设第 \(i\) 次操作时会进行的轮数为 \(0 \sim T_i\),这可以用每一维还剩多少再除一下 \(a_i\) 求得,可以发现所有 \(T_i\) 一定只有小于等于两种值。

总和为:

\[\sum_{i=1}^{n} \sum_{t=0}^{T_i} \prod_{j=1}^{k} [a_j - t b_j - (r_{i,j} - l_{i,j})] \\ \]

考虑拆解 \(\prod_{j=1}^{k} [- t b_j + (a_j - r_{i,j} + l_{i,j})]\),化为一个自变量为 \(t\)\(k\) 次多项式,暴力 \(O(k^2)\) 可以做到,设拆解后第 \(i\) 次项的系数为 \(f_i\)

那么式子就变成:

\[\begin{aligned} \sum_{i=1}^{n} \sum_{t=0}^{T_i} \sum_{j=0}^k f_j t^j & = \sum_{i=1}^{n} \sum_{j=0}^k f_j \sum_{t=0}^{T_i} t^j \\ \end{aligned} \]

其中求解 \(\sum_{t=0}^{T_i} t^j\) 是一个形如 \(\sum_{i=1}^n i^k\) 的经典的连续横坐标下的 Lagrange 插值问题。

由于 \(T_i\) 最多只有两种值,你也可以高斯消元求解。

时间复杂度 \(O(nk^2)\),瓶颈在于求多项式的系数部分。

最后要注意一点细节问题:如果在某次操作时,在原第二轮就已经全部出界,那么就直接退出程序。

启示

  1. 要加强打暴力的能力,并从中找性质、推式子、化简。
  2. 化简式子不止可以直接化简,也可以在过程中加入复杂度能接受的程序暴力求解。

代码

#define Plus_Cat "walk"
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define FOR(i,a,b) for(int i(a);i<=(int)(b);++i)
#define DOR(i,a,b) for(int i(a);i>=(int)(b);--i)
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define EDGE(g,i,x,y) for(int i(g.h[x]),y(g[i].v);~i;y=g[i=g[i].nxt].v)
using namespace std;
constexpr int N(5e5+10),M(20);
namespace Modular {
#define Mod 1000000007
	int fac[M],ifac[M];
	template<class T1,class T2>constexpr auto add(T1 a,T2 b) {
		return a+b>=Mod?a+b-Mod:(a+b<0?a+b+Mod:a+b);
	}
	template<class T1,class T2>constexpr auto mul(T1 a,T2 b) {
		return (1ll*a*b%Mod+Mod)%Mod;
	}
	template<class T,class...Types>constexpr auto add(T a,Types...args) {
		return add(a,add(args...));
	}
	template<class T,class...Types>constexpr auto mul(T a,Types...args) {
		return mul(a,mul(args...));
	}
	template<class T1,class T2>T1 &toadd(T1 &a,T2 b) {
		return a=add(a,b);
	}
	template<class T1,class T2>T1 &tomul(T1 &a,T2 b) {
		return a=mul(a,b);
	}
	template<class T0,class T,class...Types>T0 &toadd(T0 &a,T b,Types...args) {
		return toadd(a,b),toadd(a,args...);
	}
	template<class T0,class T,class...Types>T0 &tomul(T0 &a,T b,Types...args) {
		return tomul(a,b),tomul(a,args...);
	}
	int Pow(int a,int b=Mod-2) {
		int res(1);
		for(a%=Mod;b;b>>=1,tomul(a,a))if(b&1)tomul(res,a);
		return res;
	}
	void Init(int lim=M-5) {
		fac[0]=1;
		FOR(i,1,lim)fac[i]=mul(fac[i-1],i);
		ifac[lim]=Pow(fac[lim]);
		DOR(i,lim,1)ifac[i-1]=mul(ifac[i],i);
	}
} using namespace Modular;
int n,m,ans;
int c[N],d[N],e[M],w[M];
bool No_Solution() {
	static int l[M],r[M];
	RCL(l,0,l,1),RCL(r,0,r,1);
	FOR(i,1,n)e[c[i]]+=d[i],tomax(r[c[i]],e[c[i]]),tomin(l[c[i]],e[c[i]]);
	FOR(i,1,m)if(r[i]-l[i]>=w[i])return 0;
	FOR(i,1,m)if(e[i])return 0;
	return 1;
}
namespace Subtask1 {
	int l[M],r[M];
	bool Check() {
		FOR(i,1,m)if(w[i]>1e5)return 0;
		return 1;
	}
	int Cmain() {
		ans=1;
		FOR(i,1,m)tomul(ans,w[i]);
		while(true)FOR(i,1,n) {
			e[c[i]]+=d[i],tomin(l[c[i]],e[c[i]]),tomax(r[c[i]],e[c[i]]);
			int s(1);
			FOR(j,1,m) {
				if(r[j]-l[j]>=w[j])return printf("%d\n",ans),0;
				tomul(s,w[j]-(r[j]-l[j]));
			}
			toadd(ans,s);
		}
		return 0;
	}
}
namespace Subtask {
	int a[M],b[M],h[M];
	int f[2][M],l[N][M],r[N][M];
	namespace Lagrange {
		int y[M],suf[M],pre[M];
		vector<int> Pr;
		bitset<M> ip;
		void Eul(int lim,int k) {
			Pr.clear(),ip.reset(),ip[0]=ip[1]=1,y[1]=1;
			FOR(i,2,lim) {
				if(!ip[i])Pr.push_back(i),y[i]=Pow(i,k);
				for(const int &j:Pr) {
					if(i*j>lim)break;
					ip[i*j]=1,y[i*j]=mul(y[i],y[j]);
					if(!(i%j))break;
				}
			}
			FOR(i,2,lim)toadd(y[i],y[i-1]);
		}
		int F(int x,int k) {
			Eul(k+2,k);
			if(x<=k+2)return y[x];
			int ans(0);
			pre[0]=suf[k+3]=1;
			FOR(i,1,k+2)pre[i]=mul(pre[i-1],x-i);
			DOR(i,k+2,1)suf[i]=mul(suf[i+1],x-i);
			FOR(i,1,k+2)toadd(ans,mul((k+2-i)&1?Mod-1:1,y[i],pre[i-1],suf[i+1],ifac[i-1],ifac[k+2-i]));
			return ans;
		}
	} using namespace Lagrange;
	int Cmain() {
		Init();
		FOR(i,1,n) {
			e[c[i]]+=d[i];
			FOR(j,1,m)l[i][j]=l[i-1][j],r[i][j]=r[i-1][j];
			tomin(l[i][c[i]],e[c[i]]),tomax(r[i][c[i]],e[c[i]]);
		}
		FOR(j,1,m)a[j]=w[j]-(r[n][j]-l[n][j]);
		FOR(i,0,n) {
			int s(1);
			FOR(j,1,m)tomul(s,max(0,w[j]-(r[i][j]-l[i][j])));
			toadd(ans,s);
		}
		FOR(i,1,n)FOR(j,1,m)tomin(l[i][j]+=e[j]-l[n][j],0),tomax(r[i][j]+=e[j]-r[n][j],0);
		FOR(j,1,m)b[j]=r[n][j]-l[n][j];
		int last(-1);
		FOR(i,1,n) {
			RCL(f[0]+1,0,int,m),f[0][0]=1;
			int tmp(INF);
			FOR(j,1,m) {
				int K(-b[j]),B(a[j]-(r[i][j]-l[i][j]));
				if(B<=0)return printf("%d\n",ans),0;
				if(b[j]>0)tomin(tmp,B/b[j]);
				FOR(k,0,m) {
					f[j&1][k]=mul(f[!(j&1)][k],B);
					if(k)toadd(f[j&1][k],mul(f[!(j&1)][k-1],K));
				}
			}
			if(last!=tmp) {
				last=tmp,h[0]=tmp+1;
				FOR(j,1,m)h[j]=F(tmp,j);
			}
			FOR(j,0,m)toadd(ans,mul(h[j],f[m&1][j]));
		}
		printf("%d\n",ans);
		return 0;
	}
}
int main() {
#ifdef Plus_Cat
	freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
	scanf("%d%d",&n,&m);
	FOR(i,1,m)scanf("%d",&w[i]);
	FOR(i,1,n)scanf("%d%d",&c[i],&d[i]);
	if(No_Solution())return puts("-1"),0;
	RCL(e,0,e,1);
//	if(Subtask1::Check())return Subtask1::Cmain();
	return Subtask::Cmain();
}

posted @ 2024-11-13 13:46  Add_Catalyst  阅读(63)  评论(0)    收藏  举报