【ARC101F】Robots and Exits 树状数组优化DP

ARC101F Robots and Exits 树状数组

有 $ n $ 个机器人和 $ m $ 个出口。这 $ n $ 个机器人的初始位置是 $ a_1,a_2.....a_n $ ,这 $ m $ 个出口的位置是 $ b_1,b_2.....b_m $ 。你每次可以让所有机器人往左走一步或往右走一步。当一个机器人所在的位置有一个出口时,这个机器人就会从这个出口出去。问你有多少种让机器人全部离开的方案。两种方案不同当且仅当有至少一个机器人从不同的出口出去。 $ n,m≤100000 $



$ solution: $

首先我们可以将所有在最左端出口以左,最右端出口以右的机器人删掉,他们的最终出口只有一个,不影响答案。

然后我们考虑对于机器人 $ i $ ,它到左边距离它最近的出口的距离为 $ x_i $ ,如果我们往左移的距离超过 $ x_i $ 机器人就会掉进左边这个出口 ;同理设它到右边距离它最近的出口的距离为 $ y_i $ ,如果我们往右移的距离超过 $ y_i $ 机器人就会掉进右边这个出口。为了方便理解,我们先将它放到平面上:

我们考虑这个二维平面的意义:如果我们单纯将机器人左移或右移一个单位,会做很多无用功。但是我们发现,如果我们设 $ (l,r) $ 表示我向左移的最远 $ l $ 步向右移的最远 $ r $ 步 ,我们只有将最远步数向外扩展+1,才可能会有机器人掉进出口。而我们如果将这个步数也放到平面上,因为 $ l $ 和 $ r $ 的不断增大,会产生一个折线(如下图一)。我们发现这个折线如果经过某些平行于 $ y $ 轴的直线,它所代表的实际意义就是:对应的机器人会在这条折线与直线相交的时候掉入左边的出口(即当 $ l==x_i $ 时机器人 $ i $ 到达左边出口)

同理,我们发现这个折线如果经过某些平行于 $ x $ 轴的直线,其实际意义为:对应的机器人会在这条折线与直线相交的时候掉入右边的出口(即当 $ r==y_i $ 时机器 $ i $ 到达右边的出口)(如下图二)

我们再仔细观察一下这个平面所能带给我们的信息,我们发现这样一个性质:折线单调递增。折线通过竖直的直线时(继续上图一),这个点一定在折线上方对应的机器人从左边出口掉落;折线通过水平的直线时(继续上图二),这个点一定在折线下方对应的机器人从右边出口掉落。这是一个十分有用的性质!因为我们的所求的方案不同,当且仅当有至少一个机器人从不同的出口出去,我们将这个题意转入平面中,意思就是折线上下的点集不同

然后我们的DP就要登场辣!(好吧,请忽略这个中二病语气)首先我们要设出状态,我们可以根据折线每一次向上走设出状态(只有向上走越过某一条直线才会改变状态),我们设 $ f[i] $ 表示折线最后一次向右转后第一个包括的点为 $ i $ 的所有方案(这个点是所有折线下面的点里最高的点,有多个最高就取最左端的点)。这样设状态不会重复我们需要仔细思考这样设状态的意义。(这个状态转移是比较难想到的!因为要不重不漏)

然后我们考虑怎么转移:以下图为例,我们的 $ f[i] $ 是包括了点 $ i $ 的,我们如果让折线在包括点 $ i $ 后将状态转移到 $ j $ ,我们必须保证移动过程中 $ j $ 是折线最后一次向右转后第一个包括的点。于是我们可以继续向右移直到这个点 $ j $ 的竖直直线,我们立即向上转,再用折线从 $ j $ 水平直线越过后立即向右转。如下图: $ f[1] $ 可以转移到 $ f[5],f[4],f[3] $ ,所有在它右上方的点都可以!(这样的实际意义就是,我们原本 $ f1 $ 中在点1直接向右转就不往上了,意义是点345都从左边出口出去,现在我们用折线包括后点345分别变成从右边出口出去!)

同理,我们的 $ f2 $ 也可以转移到所有在它右上方的点!

于是我们发现转移方程其实就是这样:每个点的状态可以通过它左下方的点转移过来(我们上面讨论的是转移过去)

$ f[i]=f[j]+1 \quad (x_i>x_j,y_i>y_j) $

这个可以用树状数组维护,我们先将所有点按照纵坐标为第一关键字从低到高排序横坐标为第二关键字从右到左(从右到左是为了方便转移不重复,注意上面转移方程是两个小于号,我们平面里同一高度左边的点不能转移到右边,它不满足保证移动过程中 $ i $ 是折线最后一次向右转后第一个包括的点,第一个仍旧是原来那一个)。具体实现时,我们直接按顺序枚举所有点的状态,然后按照横坐标将每个点 $ i $ 对应的 $ f[i] $ 存进树状数组,因为我们的枚举的高度从低到高,那么后面我查询时直接在树状数组上查找横坐标-1的前缀和,即可完成转移!



$ code: $

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>

#define ll long long
#define db double
#define rg register int

using namespace std;

const int mod=1e9+7;//998244353;

int n,m;
int tt,ans=1; //注意赋了初值1
int a[500005];
int b[500005];
int k[500005]; //离散化
int f[500005]; //计数
int tr[500005]; //树状数组

struct su{
	int x,y;
	inline bool operator <(const su &z)const{
		if(y==z.y)return x>z.x;
		return y<z.y;
	}
}s[500005];

inline int qr(){
	register char ch; register bool sign=0; rg res=0;
	while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
	while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
	if(sign)return -res; else return res;
}

inline void add(int x,int v){ //树状数组加入
	for(;x<=tt;x+=x&-x) (tr[x]+=v)%=mod;
}

inline int ask(int x){ //树状数组查询
	rg res=0;
	for(;x;x-=x&-x) (res+=tr[x])%=mod;
	return res;
}

int main(){
	//freopen("robot.in","r",stdin);
	//freopen("robot.out","w",stdout);
	n=qr(); m=qr();
	for(rg i=1;i<=n;++i) a[i]=qr();
	for(rg i=1;i<=m;++i) b[i]=qr(); //已经按顺序排序
	for(rg i=1,j=1;i<m&&j<=n;++i){
		while(j<=n&&a[j]<=b[i])++j; //找到中间的第一个机器人
		if(j>n)break;
		while(j<=n&&a[j]<b[i+1]){ //遍历所有在中间的机器人
			k[++tt]=a[j]-b[i]; //k数组是用来离散化的
			s[tt]=su{k[tt],b[i+1]-a[j]}; ++j; //记录左右距离
		}
	} sort(k+1,k+tt+1); //离散化
	for(rg i=1;i<=tt;++i)
		s[i].x=lower_bound(k+1,k+tt+1,s[i].x)-k; //离散化
	sort(s+1,s+tt+1); //按纵坐标从小到大,横坐标从大到小
	for(rg i=1;i<=tt;++i){
		if(s[i].x==s[i-1].x&&s[i].y==s[i-1].y)continue; //去重!
		f[i]=(ask(s[i].x-1)+1)%mod; //只有横坐标比它小的才可以转移
		ans=(ans+f[i])%mod; //计入答案
		add(s[i].x,f[i]); //加入树状数组
	}
	printf("%d\n",ans);
	return 0;
}

posted @ 2019-08-04 18:21  一只不咕鸟  阅读(476)  评论(1编辑  收藏  举报