[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\) 一定只有小于等于两种值。
总和为:
考虑拆解 \(\prod_{j=1}^{k} [- t b_j + (a_j - r_{i,j} + l_{i,j})]\),化为一个自变量为 \(t\) 的 \(k\) 次多项式,暴力 \(O(k^2)\) 可以做到,设拆解后第 \(i\) 次项的系数为 \(f_i\)。
那么式子就变成:
其中求解 \(\sum_{t=0}^{T_i} t^j\) 是一个形如 \(\sum_{i=1}^n i^k\) 的经典的连续横坐标下的 Lagrange 插值问题。
由于 \(T_i\) 最多只有两种值,你也可以高斯消元求解。
时间复杂度 \(O(nk^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();
}

浙公网安备 33010602011771号