22牛客多校5 - Don‘t Starve(DP,依靠边更新端点)

https://ac.nowcoder.com/acm/contest/33190/A

题意
给定二维坐标中的 n 个点,每个点有坐标 ( x i , y i ) (x_i, y_i) (xi,yi)
现有一人从 (0, 0) 点出发,每次径直走向一个节点。要求走过路径中连续两点间距离严格递减。
问,最多能走多少个节点?

1 ≤ N ≤ 2000 , − 20000 ≤ x i , y i ≤ 20000 1≤N≤2000,−20000≤xi,yi≤20000 1N2000,20000xi,yi20000

思路
如果修改下题目,让走过路径中的点权逐渐减小,那么可以用拓扑序+dp,大点权节点向小点权节点连边,每次删掉入度为0一个大节点更新小节点状态。如果起点确定的话,需要将其余所有节点状态初始化为负无穷,表示从这个节点出发是非法的。这样所有节点状态中的最大值便是最多能走的节点数。

然后回到此题,要求的是边权递减,那么就不能这样做了。

考虑 DP。
定义 f[i] 表示,从起点出发到达 i 点时走的最多步数。
把所有的有向边都存下来(无向边拆成两个有向边),然后从大到小排序。

  • 如果各个边权都不相同的话,对于一条边从 x 到 y,那么 f[y] 就可以用 f[x] + 1 来更新。因为 f[x] 是用前面边权较大的边中的点来更新的,保证了边权递减。
  • 但是如果有几条边边权相同的话,如果 f[y] 由 f[x] 更新之后,再去更新 f[z],那么 f[z] 的状态数就 +2,而本只能 +1,就存在串着改的问题。为了防止出现串着改,那么就让这些边权相同的边同时更新。
    具体的方式是,先把更新过的 f[y] 都分别存成 t[y]t[y] = max(t[y], f[x] + 1),等边权相同的边都更新完毕后,再将更新后的值 t[y] 赋值给 f[y],这样就避免了更改之后的 f[y] 再串着改其他点。

然后,只能由起点向其他点连边,其他点不能连向起点(否则一直来回更新)。

同样,把除了起点之外的所有节点初始状态赋值为负无穷,表示该点作为起点非法。

Code

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long
#define PII pair<int,int>
#define fi first
#define se second
#define endl '\n'

const int N = 200010, mod = 1e9+7;
int T, n, m;
PII a[N];
struct node{
	int x, y;
	double dis;
}edg[N];
int t[N], f[N];

bool cmp(node a, node b){
	return a.dis > b.dis;
}

double dis(int x, int y)
{
	int a1 = a[x].first, b1 = a[x].second;
	int a2 = a[y].first, b2 = a[y].second;
	return (a1 - a2)*(a1 - a2) + (b1 - b2)*(b1 - b2);
}

signed main(){
	Ios;
	cin >> n;
	
	for(int i=1;i<=n;i++) cin >> a[i].first >> a[i].second;
	
	int idx = 0;
	for(int i=0;i<=n;i++) //从i走向j 
	{
		for(int j=1;j<=n;j++) //不能回到0,只能从0往外走 
		{
			if(i == j) continue;
			edg[++idx] = {i, j, dis(i, j)};
		}
	}
	
	sort(edg+1, edg+idx+1, cmp);
	
	for(int i=1;i<=n;i++) f[i] = -1e9; //除了起点,其余所有点都初始为负无穷 
	
	for(int i=1;i<=idx;i++)
	{
		int r = i;
		t[edg[i].y] = -1e9; //双指针找到所有边权相同的边一起更新
		while(r + 1 <= idx && edg[r + 1].dis == edg[i].dis){
			r ++;
			t[edg[r].y] = -1e9;
		}
		//把所有相同边权的一起更新,防止串着改 
		for(int j=i;j<=r;j++) 
		{
			int x = edg[j].x, y = edg[j].y;
			t[y] = max(t[y], f[x] + 1);
		}
		for(int j=i;j<=r;j++)
		{
			int x = edg[j].x, y = edg[j].y;
			f[y] = max(f[y], t[y]);
		}
		i = r;
	}
	
	int ans = 0;
	for(int i=1;i<=n;i++) ans = max(ans, f[i]);
	cout << ans;
	
	return 0;
}

经验

  • 把所有边单独拿出来,然后把两个端点来更新,这样的操作好像之前见过,但是看到这道题的时候完全没有这个想法。。以后见到应该会想到吧。
  • 另外那个把若干个点同时更新的实现方式确实很妙。

很好的一道题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值