【AT4353】[ARC101D] Robots and Exits(树状数组优化DP)
- 一根数轴上有\(n\)个机器人和\(m\)个出口。
- 一次操作可以将所有机器人同时向左或向右移动一个位置,当一个机器人到达某个出口时会立刻离开。
- 求有多少种不同的操作序列,定义两种操作序列不同当且仅当至少一个机器人离开的出口不同。
- \(n,m\le10^5\)
抽象至坐标系
显然,一个机器人只可能从左右离它最近的这两个出口离开。(若左右只有一个出口,那么这个机器人离开的出口唯一,可以直接忽略)
我们对于每个机器人,计算出它到左右两个出口各自的距离\((x_i,y_i)\)。
那么,如果某一刻向左走的步数达到了\(x_i\),这个机器人就会从左边出去;如果某一刻向右走的步数达到了\(y_i\),这个机器人就会从右边出去。
由于这里的步数指的是历史最大步数,显然不会减少,因此一种操作序列可以看作一条从原点出发只能向上或向右走的路径。
此时路径左上方的所有点(包括路径上向上到达的点)对应的机器人都会从左边出去,路径右下方的所有点(包括路径上向右到达的点)对应的机器人都会从右边出去。
但不同的路径可能会走出相同的划分,因此需要进一步转化。
树状数组优化\(DP\)
这种路径显然有一个转化,用经过的点来表示折线。
因此每个点都可以从它左下方所有点转移。
那么我们初始把所有点按横坐标从小到大排序,相同时按纵坐标从大到小排序(防止相同纵坐标之间的转移),那么每个点的转移实际上就相当于一个询问前缀和的过程。
直接树状数组优化即可。
代码:\(O(nlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define X 1000000007
using namespace std;
int n,m,a[N+5],b[N+5],dc,dv[N+5];
struct Data
{
int x,y;I Data(CI a=0,CI b=0):x(a),y(b){}
I bool operator < (Con Data& o) Con {return x^o.x?x<o.x:y>o.y;}
I bool operator == (Con Data& o) Con {return x==o.x&&y==o.y;}
}s[N+5];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
char oc,FI[FS],*FA=FI,*FB=FI;
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
struct TreeArray
{
int a[N+5];I void U(RI x,CI v) {++x;W(x<=dc+1) a[x]=(a[x]+v)%X,x+=x&-x;}//单点修改
I int Q(RI x,RI t=0) {++x;W(x) t=(t+a[x])%X,x-=x&-x;return t;}//前缀求和
}T;
int main()
{
RI i,j;for(read(n,m),i=1;i<=n;++i) read(a[i]);for(i=1;i<=m;++i) read(b[i]);
for(sort(a+1,a+n+1),sort(b+1,b+m+1),i=1;i<=n&&a[i]<b[1];++i);//最左出口左边的点忽略
RI t=0;for(j=1;i<=n&&j^m;++j) W(i<=n&&a[i]<b[j+1]) s[++t]=Data(a[i]-b[j],dv[++dc]=b[j+1]-a[i]),++i;//求出每点两端出口,最右出口右边的点忽略
sort(s+1,s+t+1),t=unique(s+1,s+t+1)-s-1,sort(dv+1,dv+dc+1),dc=unique(dv+1,dv+dc+1)-dv-1;//排序+去重
for(T.U(0,1),i=1;i<=t;++i) s[i].y=lower_bound(dv+1,dv+dc+1,s[i].y)-dv,T.U(s[i].y,T.Q(s[i].y-1));//从左下方的点转移
return printf("%d\n",T.Q(dc)),0;//输出总和
}
待到再迷茫时回头望,所有脚印会发出光芒