SHOI2003吃豆豆

原题链接对,我就是懒得抄题了
第一眼看过去, 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值