https://ac.nowcoder.com/acm/contest/33187/L
题意
给定 n 个世界,每个世界里有 m 个节点,
l
i
l_i
li 条边。
玩家初始在第一世界的 1 号节点。
在每个世界中,玩家要么选择待在当前节点不动,要么经过和该节点相连的一条边到达另一个节点(仅一次)。
然后,传送到下一个世界,玩家所在节点编号不变。
如果在最后一个世界,玩家所在 m 号节点,则胜利。
问,从这 n 个世界中,最少选出多少个连续的世界能够使得玩家胜利?
1
≤
n
≤
1
0
4
,
2
≤
m
≤
2
×
1
0
3
,
0
≤
l
i
≤
m
×
(
m
−
1
)
1≤n≤10^4,\ 2≤m≤2×10^3,\ 0≤l_i≤m×(m−1)
1≤n≤104, 2≤m≤2×103, 0≤li≤m×(m−1)
∑
l
i
≤
1
0
6
\sum l_i \leq 10^6
∑li≤106
思路
当前世界只能由上个世界的节点到达,那么这个世界就用上个世界来转移。
定义 f[i, j]
表示,到达世界 i
的节点 j
最少使用的世界数。
两种转移方式:
f[i, j] = min(f[i, j], f[i-1, j] + 1)
;f[i, y] = min(f[i, y], f[i, x])
;这样转移看似没有问题,但是注意到题目中限制在每个世界中最多只能经过边一次,而第二种转移方式可能在更新过 f[i, y]
之后,y
节点再去更新别的节点,这样就会串着改,不能保证只经过边一次了! 所以第二种转移方式不对。
如何保证只经过边一次呢?
可以直接从上一个世界的节点 x
下手,用上一个世界的 x
状态直接更新当前世界的 y
。上个世界的 x 呆着不动转移过来花费 1,在这个世界有一条连边花费 0,所以整个花费就为 1:f[i, y] = f[i-1, x] + 1
。
同时要注意:
所以整个状态转移方程为:
for(int j=1;j<=m;j++) f[0][j] = 1e9;
f[0][1] = 1;
int ans = 1e9;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++) f[i][j] = f[i-1][j] + 1; //呆着不动
f[i][1] = 1;
int k; cin >> k;
while(k--)
{
int x, y; cin >> x >> y;
if(x == 1) f[i][y] = 1; //注意和节点1相连的情况
else f[i][y] = min(f[i][y], f[i-1][x] + 1); //依靠连边移动一次
}
ans = min(ans, f[i][m]);
}
if(ans >= 1e9) cout << -1;
else cout << ans;
然后用滚动数组优化掉第一维即可:
#include
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long
#define endl '\n'
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
int f[2010], t[2010];
signed main(){
Ios;
cin >> n >> m;
for(int i=2;i<=m;i++) f[i] = t[i] = 1e9;
f[1] = t[1] = 1;
int ans = 1e9;
for(int i=1;i<=n;i++)
{
int k; cin >> k;
for(int j=1;j<=m;j++) f[j] = min(f[j], t[j] + 1);
while(k--){
int x, y; cin >> x >> y;
if(x == 1) f[y] = 1;
else f[y] = min(f[y], t[x] + 1);
}
ans = min(ans, f[m]);
for(int j=2;j<=m;j++) t[j] = f[j], f[j] = 1e9;
t[1] = f[1] = 1;
}
if(ans != 1e9) cout << ans;
else cout << -1;
return 0;
}