原题链接(对,我就是懒得抄题了 )
第一眼看过去, dp ,再一看, 2个peaman, 多开几维而已, 也还好,但看一眼数据范围,没给x和y的取值, 这就很烦人了, 如果开x和y的二维数组存是有点会re的, 这就说明它的x和y真的能到很大。所以要解决的第一个问题就是怎么存这些豆豆。
先清掉一个废条件
题上说beanman的路径不能相交, 如图是不行的
但如果,是这样的
它不就成立了吗, 所以我们可以把相交的部分以上的路径看成一个beanman的, 以下的路径看成另一个beanman的, 所以路径不相交这个条件是真的没有用。
存图
我们来换个思路, 题目中给出了数据说最多有2000个点, 只有经过这些有豆豆的点才会对答案产生影响, 其他点与答案的产生无瓜, 那我们只存这些有豆豆的点不就好了,至于从点i能到的点j, 一定存在xi<=xj&&yi<=yj, 毕竟beanman只能向上走或向右走, 那么我们用邻接表来存一下这个重新构过了的图
struct Point{
int x, y;
bool operator <(const Point &b)
{
return x<b.x || x==b.x&&y<b.y;
}
}point[N];
struct Edge{
int u, v, nxt;
Edge(int _u=0, int _v=0, int _n=0):u(_u), v(_v), nxt(_n){}
}edge[2*M];
int head[M], n;
void addedge(int u, int v)
{
static int cnt=1;
edge[++cnt]=Edge(u, v, head[u]);
head[u]=cnt;
}
int main()
{
scanf("%d", &n);
for(int i=1; i<=n; i++) scanf("%d%d", &point[i].x, &point[i].y);
sort(point+1, point+n+1);
for(int i=1; i<=n; i++)
{
int mx=2e9;
for(int j=i+1; j<=n; j++)
{
if(point[i].y<=point[j].y&&point[j].y<mx)
mx=point[j].y, addedge(i, j);
}
}
由于我们要保证最优解, 不能说让beanman一下子从起点跑到终点, 中间的点一个也不过, 所以在这里mx是来保证我每次跟点i连上的点不能和i间有其他的可以连的点
但是这样存图的话要想去查答案得从一条一条链的起点开始跑, 还不知道何时该结束, 那我们不如定义两个虚点, 一个指向所有起点, 一个被所有终点指向, 那答案不就好存了嘛, 直接在最后的虚点上了, 所以在代码中加上
int s=0, t=n+1;
for(int i=1; i<=n; i++)
addedge(s, i), ++in[i], addedge(i, t), ++in[t];
这样对于存图的问题就解决了, 这时我们发现这个图是一个拓扑图, 只有访问了前面的点才能到后面的点, 再给它来个拓扑排序
int cnt, id[M], tid[M], in[M];
void tpsort(int s)
{
queue<int> q;
q.push(s);
while(q.size())
{
int u=q.front();
q.pop();
id[u]=++cnt, tid[cnt]=u;
for(int i=head[u]; i; i=edge[i].nxt)
{
if(--in[edge[i].v]==0) q.push(edge[i].v);
}
}
}
//然后把输入后的存图处理改成这样
for (int i = 1; i <= n; ++i) {
int mx = 2e9;
for (int j = i+1; j <= n; ++j)
if (nd[i].y<=nd[j].y && nd[j].y<mx)
mx = nd[j].y, ++in[j], addEdge(i, j);
}
这样我们就可以顺着它的拓扑序来dp了
dp
首先来确定状态, 我们已经进行了拓扑排序,前面的必定能推向后面的, 因此只需要一个拓扑序来代表一个beanman所在的点就好了, 2个beanman自然是两个状态分别存两个beanman的位置
然后来看状态转移方程, u!=v? f[v][u]=max(f[v][u], f[x][y]+1): f[v][u]=max(f[v][u], f[x][y]);
其中x,y表示原来位置, u,v表示现在为止, 为了偷懒 ,我们保证v要大于u ,那么代码就出来了
void dp(int x, int y)
{
for(int i=head[x]; i; i=edge[i].nxt)
{
int v=edge[i].v, u=y;
if(id[v]>id[u]) swap(u, v);
if(u!=v) f[v][u]=max(f[v][u], f[x][y]+1);
else f[v][u]=max(f[v][u], f[x][y]);
}
}
//main函数里
for(int i=1; i<=cnt; i++)
for(int j=i; j<=cnt; j++)
dp(tid[i], tid[j]);
那么最后输出f[n+1][n+1]就是最优解了
(上面废话的总结) 代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
const int N=2e3+10, M=1e5+7;
struct Point{
int x, y;
bool operator <(const Point &b)
{
return x<b.x || x==b.x&&y<b.y;
}
}point[N];
struct Edge{
int u, v, nxt;
Edge(int _u=0, int _v=0, int _n=0):u(_u), v(_v), nxt(_n){}
}edge[2*M];
int head[M], n, f[N][N], cnt, id[M], tid[M], in[M];
void addedge(int u, int v)
{
static int cnt=1;
edge[++cnt]=Edge(u, v, head[u]);
head[u]=cnt;
}
void tpsort(int s)
{
queue<int> q;
q.push(s);
while(q.size())
{
int u=q.front();
q.pop();
id[u]=++cnt, tid[cnt]=u;
for(int i=head[u]; i; i=edge[i].nxt)
{
if(--in[edge[i].v]==0) q.push(edge[i].v);
}
}
}
void dp(int x, int y)
{
for(int i=head[x]; i; i=edge[i].nxt)
{
int v=edge[i].v, u=y;
if(id[v]>id[u]) swap(u, v);
if(u!=v) f[v][u]=max(f[v][u], f[x][y]+1);
else f[v][u]=max(f[v][u], f[x][y]);
}
}
int main()
{
scanf("%d", &n);
for(int i=1; i<=n; i++) scanf("%d%d", &point[i].x, &point[i].y);
sort(point+1, point+n+1);
for(int i=1; i<=n; i++)
{
int mx=2e9;
for(int j=i+1; j<=n; j++)
{
if(point[i].y<=point[j].y&&point[j].y<mx)
mx=point[j].y, in[j]++, addedge(i, j);
}
}
int s=0, t=n+1;
for(int i=1; i<=n; i++)
addedge(s, i), ++in[i], addedge(i, t), ++in[t];
tpsort(s);
for(int i=1; i<=cnt; i++)
{
for(int j=i; j<=cnt; j++)
dp(tid[i], tid[j]);
}
printf("%d\n", f[t][t]-1);
return 0;
}