标签:坐标系 ons names 最大值 isp its for 其他 tin
在数轴上有\(n\)个小球和\(m\)个洞。每次操作你可以将所有小球整体往左一格或整体往右一格,当落入洞中后,就不再会移动。当所有小球都掉进洞中是游戏结束。两种方案被认为不同当且仅当存在一个小球掉进了不同的洞中。
问总方案数。
\(n,m\le100000\)
性质一:一个小球只会掉进最靠近它的左右两边的洞中。
性质二:一个操作是有效的(即可能有小球落入洞中),当且仅当它改变了向左移动的最大值或者向 右移动的最大值。
性质三:对于一个小球,它的状态只取决于它先掉入哪个洞中,而这又取决于它向左移动的最大值和 向右移动的最大值哪个先到达对应的数值。
于是,我们以向左移动的最大值为\(x\)轴,向右移动的最大值为\(y\)轴,建立平面直角坐标系。
性质二说明:每次只能向上或向右走,所有的操作最终形成一条路径。
根据性质三,不妨设一个小球到它左边的距离为\(a\),到右边的距离为\(b\),那么我们就在坐标系中加入一个\((a,b)\)的点,那么该点落入哪个洞中取决于操作形成的路径与以原点为左下角,\((a,b)\)为右上角的矩形的两边相交。
不难发现,所有在这条路径上方的点都落入左边的洞中,其他落入右边的洞中。
也就是说,用一条只能向上或者向右的路径将平面上的点划分为两个集合的本质不同方案数。
因为只与划分的集合有关,所以我们不妨让路径贴着点走。
设\(dp_i\)表示最后一次经过的点为\(i\)的方案数,则有
\[
dp_i=1+\sum_{x_j<x_i,y_j<y_i}dp_j
\]
用扫描线+树状数组维护即可
#include<bits/stdc++.h>
using namespace std;
const int sz=2e5+7;
const int mod=1e9+7;
int n,m;
int ans;
int t,len;
int f[sz];
int dp[sz];
int d[sz];
int a[sz],b[sz];
void upd(int &x,int y){
x=x+y>=mod?x+y-mod:x+y;
}
void add(int x,int sum){
while(x<=len){
upd(f[x],sum);
x+=x&-x;
}
}
int query(int x){
int ret=0;
while(x){
upd(ret,f[x]);
x-=x&-x;
}
return ret;
}
struct node{
int x,y;
const bool operator <(const node& p)const{
if(x!=p.x) return x<p.x;
else return y>p.y;
}
}c[sz];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=m;i++) scanf("%d",&b[i]);
int now=1;
b[m+1]=INT_MAX;
for(int i=1;i<=n;i++){
if(now==1&&a[i]<=b[now]) continue;
while(a[i]>=b[now+1]) now++;
if(now==m) break;
c[++t]=(node){a[i]-b[now],b[now+1]-a[i]};
d[++len]=a[i]-b[now],d[++len]=b[now+1]-a[i];
}
sort(d+1,d+len+1);
len=unique(d+1,d+len+1)-d-1;
for(int i=1;i<=t;i++){
c[i].x=lower_bound(d+1,d+len+1,c[i].x)-d;
c[i].y=lower_bound(d+1,d+len+1,c[i].y)-d;
}
sort(c+1,c+t+1);
for(int i=1;i<=t;i++){
if(c[i].x==c[i-1].x&&c[i].y==c[i-1].y) continue;
dp[i]=(1+query(c[i].y-1))%mod;
upd(ans,dp[i]);
add(c[i].y,dp[i]);
}
upd(ans,1);
printf("%d\n",ans);
}
标签:坐标系 ons names 最大值 isp its for 其他 tin
原文地址:https://www.cnblogs.com/river-flows-in-you/p/12186102.html