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 1≤N≤2000,−20000≤xi,yi≤20000
思路
如果修改下题目,让走过路径中的点权逐渐减小,那么可以用拓扑序+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;
}
经验
- 把所有边单独拿出来,然后把两个端点来更新,这样的操作好像之前见过,但是看到这道题的时候完全没有这个想法。。以后见到应该会想到吧。
- 另外那个把若干个点同时更新的实现方式确实很妙。
很好的一道题。