• 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
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    经验

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

    很好的一道题。

  • 相关阅读:
    合并回文子串(区间dp)
    时间复杂度吐血总结
    springboot配置过滤器和多个拦截器、执行顺序
    如何借助低代码开发平台 YonBuilder 填补应用开发 “产能缺口”?
    1B踩坑大王
    电动汽车充放电V2G模型MATLAB代码
    专用嵌入式分析软件的重要性
    【python爬虫笔记】验证码
    华为手机如何开启设置健康使用手机模式限制孩子玩手机时间?
    Android 11源码——安全策略SELinux关闭
  • 原文地址:https://blog.csdn.net/Mr_dimple/article/details/126709975