2022 牛客多校2 L,5 A(分层图dp,对边做dp)
2022 牛客多校2 L,5 A(对边dp,分层图dp)
题意
给 \(n\) 个点,和原点,原点到其他点单向,其他点之间可互达。边权是两点间的距离。
要求走到下一个点的距离严格小于上一次移动距离。求走的最大步数。
思路
乍一看因为可以成环无法dp。但仔细想一想可以发现每条边至多使用一次。所以对边dp是没有后效性的。
考虑 \(f[i]\) 表示最后走到 \(i\) 的最大步数。一开始我们位于 \(0\) ,初始状态为 \(f[0]=0\) 其余状态非法,设为无穷小。
考虑转移,我们令 \(g[i]\) 表示下一个状态。
如果有一条 \((u,v)\) 的边,会进行 \(g[v] = max(g[v],f[u]+1)\) 的转移。
因为要保证每次走的边比上一步短,因此事先要对边排序。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<functional>
#include<random>
#include<iomanip>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
#define int long long
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
using PII = array<int,2>;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;
void solve()
{
int n; cin >> n;
vector<PII>a(n + 1);
for(int i = 1;i <= n;i += 1)
cin >> a[i][0] >> a[i][1];
auto getDis2 = [&](int x,int y) {
int dx = a[x][0] - a[y][0];
int dy = a[x][1] - a[y][1];
return dx * dx + dy * dy;
};
vector<array<int,3>> e;
for(int i = 0;i <= n;i += 1) {
for(int j = 1;j <= n;j += 1) {
if(i == j) continue;
e.push_back({getDis2(i,j),i,j});
}
}
sort(e.begin(),e.end());
reverse(e.begin(),e.end());
int m = e.size();
vector<int> f(n + 1,-linf),g(n + 1,-linf);
f[0] = 0;
for(int i = 0;i < m;i += 1) {
int j = i;
vector<int> s;
while(j < m and e[i][0] == e[j][0]) {
auto [_,u,v] = e[j];
g[v] = max(g[v],f[u] + 1);
s.push_back(v);
j += 1;
}
for(auto v : s)
f[v] = g[v];
i = j - 1;
}
cout << *max_element(f.begin(),f.end());
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
//int T;cin>>T;
//while(T--)
solve();
return 0;
}
这个dp和前两场的分层图dp很像,
L-Link with Level Editor I_"蔚来杯"2022牛客暑期多校训练营2 (nowcoder.com)
题意
林克正在玩一个游戏,叫做NIO的游戏。
在这个游戏中,一个关卡由几个世界组成。每个世界由 \(m\) 个节点和一些定向道路组成。玩家从第一个世界的第 \(1\) 个节点开始。在每个世界中,玩家可以停留在当前的节点上,或者正好通过该世界中存在的一条道路。之后,玩家将被传送到下一个世界,而不改变他所停留的节点的 \(ID\)。如果没有下一个世界,游戏结束。如果玩家在节点 \(m\) 上结束,他就赢了。
林克正在编辑一个新的关卡,他已经做了 \(n\) 个世界,想选择其中一个连续的子段来组成一个新的关卡。唯一的限制是,至少要有一种获胜的方式。
林克不想使用太多的世界。在新的关卡中,林克需要使用的最少世界数是多少?
思路
如果我们把走向下一个世界认为是 “距离” 变大。翻译一下就是在一个图上,每次我们 走的距离比上一次大 。求最小步数。
此时几乎和上面那个题一样了。我们只需要对边dp,定义 \(f[i]\) 表示最后停在 \(i\) 上的最小步数dp就行了。
可以发现,该类dp问题的特征是上一步和下一步满足一个大小关系,此时我们要做的就是 按序 的处理状态。可以把它理解为在一个分层图上进行dp。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<random>
#include<iomanip>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
// #define int long long
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;
void solve()
{
int n,m; cin >> n >> m;
vector<int> dp(m + 1,0);
int ans = inf;
for(int i = 1;i <= n;i += 1) {
auto ndp = dp;
ndp[1] = i;
int l; cin >> l;
for(int j = 0;j < l;j += 1) {
int u,v; cin >> u >> v;
dp[v] = max(dp[v],ndp[u]);
}
if(dp[m]) ans = min(ans, i - dp[m] + 1);
}
cout << (ans == inf ? -1 : ans);
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
//int T;cin>>T;
//while(T--)
solve();
return 0;
}