洛谷P4066 [SHOI2003]吃豆豆

感觉和传纸条很像,听说网络流能写
然而并不想写,还是选择了拓排加DP
考虑一下这题和传纸条DP的区别
边界N.M过大,2000,无法承受 O ( N 2 ∗ M 2 ) O(N^2*M^2) O(N2M2)算法,甚至优化后的 O ( N ∗ M 2 ) O(N*M^2) O(NM2)的也不行
我们考虑改变DP顺序,因为这题的豆子是空间内的一些点,而且每次只能往上往右走,如果按照传纸条的方法DP,会求出非常多的无用量,如何避免呢,我们选择建图
对于一个豆子,我们向他右上方的可行点连一条单向边,然后进行拓扑排序,按拓扑序进行DP
但是极限情况下还是会产生4e6条边,我们考虑如何减少
很显热,如果三个点i,j,k,满足如图所示的位置关系,那么i,k之间肯定是不同连边的
在这里插入图片描述
因为 i − > j − > k i->j->k i>j>k肯定优于 i − > k i->k i>k的,好了,建边的问题解决了,那么如何DP呢
我们定义 f [ i ] [ j ] f[i][j] f[i][j]表示P1吃到第 i i i个豆子,P2吃到第 j j j个豆子,获得的最大分数
为什么可以这么设定呢?
还是考虑上面三个豆子, f [ j ] [ k ] = ? f[j][k]=? f[j][k]=?显然有两种,一种是原来的,或者由 f [ i ] [ k ] + 1 f[i][k]+1 f[i][k]+1转移而来
换成一般式就是 f [ x t o ] [ y ] = m a x ( f [ x t o ] [ y ] , f [ x ] [ y ] + 1 ) f[x_{to}][y]=max(f[x_{to}][y],f[x][y]+1) f[xto][y]=max(f[xto][y],f[x][y]+1)
但是如果 x t o x_{to} xto y y y相等怎么办这时显然不能再更新了,因为不符合豆子只能吃一次的要求
所以这时方程式就是 f [ x t o ] [ y ] = m a x ( f [ x t o ] [ y ] , f [ x ] [ y ] ) f[x_{to}][y]=max(f[x_{to}][y],f[x][y]) f[xto][y]=max(f[xto][y],f[x][y])

主体结束,我们考虑一些细节,如何保证 x t o x_{to} xto一定可以转移到 y y y
别忘了,我们统计了拓扑序,如果 x t o x_{to} xto可以转移到 y y y,那么 r d [ y ] > r d [ x t o ] rd[y]>rd[x_{to}] rd[y]>rd[xto]是一定成立的,所以, y y y的拓扑序一定在 x t o x_{to} xto后面,但如果出现了在前面的情况,我们 s w a p ( x t o , y ) swap(x_{to},y) swap(xto,y)一下就行

大概流程就是这样
上代码

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 2007;

struct node{
	int to,next;
}edge[maxn*maxn];
int cnt,head[maxn];
void add(int from,int to){
	edge[++cnt].next=head[from];
	edge[cnt].to=to;
	head[from]=cnt;
}
struct P{
	int x,y;
	friend bool operator <(P a,P b){
		return a.x!=b.x?a.x<b.x:a.y<b.y;
	}
}po[maxn];
int n,rd[maxn],st=0,ed,f[maxn][maxn];
int tot,id[maxn],th[maxn];
void toopsort(){
	queue<int>q;q.push(0);
	while(q.size()){
		int f1=q.front();
		q.pop();
		id[f1]=++tot;
		th[tot]=f1;
		for(int i=head[f1];i;i=edge[i].next)
			if(--rd[edge[i].to]==0)q.push(edge[i].to);
	}
}
void dp(int x,int y){
	for(int k=head[x];k;k=edge[k].next){
		int from=edge[k].to,to=y;
		if(id[from]>id[to])swap(from,to);
		if(from==to)f[from][to]=max(f[from][to],f[x][y]);
		else f[from][to]=max(f[from][to],f[x][y]+1);
	}
}
int main()
{
    scanf("%d",&n);ed=n+1;
    for(int i=1;i<=n;++i)
    	scanf("%d%d",&po[i].x,&po[i].y);
    sort(po+1,po+1+n);
    for(int t,i=1;i<=n;i++)
        for(int j=i+1,t=1e9;j<=n;j++)
            if(po[i].y<=po[j].y&&po[j].y<t){
                t=po[j].y;
                rd[j]++;
                add(i,j);
            }
    for(int i=1;i<=n;i++){
    	add(st,i),rd[i]++;
    	add(i,ed),rd[ed]++;
	}
	toopsort();
	/*for(int i=1;i<=n;i++)cout<<th[i]<<" ";
	cout<<endl;
	for(int i=1;i<=n;i++)cout<<id[i]<<" ";
	cout<<endl;*/
	for(int i=1;i<=tot;i++){
		for(int j=i;j<=tot;j++){
			dp(th[i],th[j]);
		}
	}
	cout<<f[ed][ed]-1<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值