Loading

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

题目大意

一个平面直角坐标系上,有\(N\)个点,标号为\(1\)\(N\),其中第i个点的坐标为\((x[i],y[i])\)
求满足以下两个条件的点列\({p[i]}\)的数目(假设\({p[i]}\)的长度为\(M\)):

  • 对任意\(1 \leq i < j \leq M\),必有\(y[p[i]] > y[p[j]]\)
  • 对任意\(3 \leq i \leq M\),必有\(x[p[i-1]] < x[p[i]] < x[p[i-2]]\)或者\(x[p[i-2]] < x[p[i]] < x[p[i-1]]\)
    求满足条件的非空序列\({p[i]}\)的数目,结果对一个整数\(Q\)取模。

\(n\leq 7000\)

分析

\(f_{i,0/1}\),表示\(i\)往左下/右下的方案数,转移的顺序成了问题。如果我们将点按照\(x\)排序,对于一个\(i\),倒序从\(i-1\)\(1\)枚举\(j\),如果\(y_j>y_i\),就把\(f_{i,0}\)转移到\(f_{j,1}\),否则将\(f_{j,1}\)转移到\(f_{i,0}\),可以发现这样转移,只会从\(y\)较小的转移到较大的,并且\(i\)转移至\(j\)时,\(i\)对应的上一个点\(k\)一定在\(i,j\)中间,因此所有的转移都是合法的。这是一种极为巧妙的转移方式。

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 7007;

int n, q, ans, f[N][2];
struct note { int x, y; } a[N];

int cmp(note p, note q) { return p.x < q.x; }

int main()
{
	scanf("%d%d", &n, &q);
	for (int i = 1; i <= n; ++i) scanf("%d%d", &a[i].x, &a[i].y);
	sort(a + 1, a + n + 1, cmp);
	for (int i = 1; i <= n; ++i)
		for (int j = i - 1; j >= 1; --j)
			if (a[j].y > a[i].y) f[j][1] = (f[j][1] + f[i][0] + 1) % q;
			else f[i][0] = (f[i][0] + f[j][1] + 1) % q;
	for (int i = 1; i <= n; i++) ans = (ans + f[i][0] + f[i][1]) % q;
	ans = (ans + n) % q;
	printf("%d\n", ans);
	return 0;
}
posted @ 2019-08-14 11:09  gz-gary  阅读(160)  评论(0编辑  收藏  举报