[JZOJ1901] 【2010集训队出题】光棱坦克

题目

题目大意

给你个平面上的一堆点,问序列 p i {p_i} pi的个数。
满足 y p i − 1 > y p i y_{p_{i-1}}>y_{p_i} ypi1>ypi并且 x p i x_{p_i} xpi x p i − 1 x_{p_i-1} xpi1 x p i − 2 x_{p_i-2} xpi2之间。


正解

我不知道为什么我的树状数组打挂了……尽管不一定能AC,但是WA了……

这题的正解有很多,最为传奇的,则是彭大爷的神仙解法。
显然这是个DP,而他抛弃了按照 y y y从大到小排序的传统做法,反而是以 x x x从小到大排序。将 p i {p_i} pi倒过来做。设 f i , 0 / 1 f_{i,0/1} fi,0/1表示到 i i i这个点,上一个点在左边或者右边的方案数。
DP的时候 i i i从左到右扫过去,然后从右到左枚举 j j j,有两种转移:
如果 y j &lt; y i y_j&lt;y_i yj<yi,则从 f j , 1 f_{j,1} fj,1转移到 f i , 0 f_{i,0} fi,0
如果 y j &gt; y i y_j&gt;y_i yj>yi,则从 f i , 0 f_{i,0} fi,0转移到 f j , 1 f_{j,1} fj,1
这样的转移为什么是对的?实际上随便画个图就能理解了。
具体来说,在第一类转移的时候,很显然之前转移到 f j , 1 f_{j,1} fj,1的是 j j j i i i之间的状态;
在第二类转移的时候,很显然之前转移到 f i , 0 f_{i,0} fi,0的是 j j j i i i之间的状态。
这样就保证了题目要求的性质。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 7010
inline int input(){
	char ch=getchar();
	while (ch<'0' || '9'<ch)
		ch=getchar();
	int x=0;
	do{
		x=x*10+ch-'0';
		ch=getchar();
	}
	while ('0'<=ch && ch<='9');
	return x;
}	
int n,m,mo;
struct Node{
	int x,y;
} d[N];
inline bool cmpd(const Node &a,const Node &b){return a.x<b.x;}
int f[N][2];
int main(){
	n=input(),mo=input();
	for (int i=1;i<=n;++i)
		d[i]={input(),input()};
	sort(d+1,d+n+1,cmpd);
	for (int i=1;i<=n;++i){
		f[i][0]=f[i][1]=1;
		for (int j=i-1;j>=1;--j)
			if (d[j].y<d[i].y)
				(f[i][0]+=f[j][1])%=mo;
			else
				(f[j][1]+=f[i][0])%=mo;
	}
	long long ans=0;
	for (int i=1;i<=n;++i)
		ans+=f[i][0]+f[i][1];
	printf("%lld\n",((ans-n)%mo+mo)%mo);
	return 0;
}

总结

这样的DP真是太鬼畜了……
彭大爷牛逼!!!
%%%

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值