算法杂记 2023/02/26
算法杂记 2023/02/26#
好久没有自己写出过一题了,尴尬。这题WA了足足 6 发才写出来。这种偏模拟一些的我还是太薄弱了。
Game on Axis 1900
#
题面翻译#
题目描述#
有 个点,第 个点上有数字 ,你在这些点上面玩一个游戏,初始在点 。当你在点 时:
- 若 ,则你会跳到点 ;
- 否则游戏结束。
在游戏开始前,你可以选择一对 和 (),并将 赋值为 。请求出这样的 的对数使得可以在有限步内结束游戏。注意即使 已经等于 了, 也可能是合法的。
输入格式#
输入第一行一个正整数 ()表示数据组数。
每组测试数据第一行一个正整数 ()表示点的个数。
第二行 个正整数 ()表示序列 。
保证所有测试数据的 之和不超过 。
样例解释#
第一组数据中,符合条件的 为 和 ,分别对应路径 和 。注意 不合法因为 , 不符合 ; 也不合法因为你将永远从 走到 而无法结束游戏。
第二组数据中,符合条件的 为 。
第三组数据中,符合条件的 为 。
题目描述#
There are points , each point has a number on it. You're playing a game on them. Initially, you are at point . When you are at point , take following steps:
- If , go to ,
- Otherwise, the game ends.
Before the game begins, you can choose two integers and satisfying , and replace with (set ). Find the number of distinct pairs such that the game that you start after making the change ends in a finite number of steps.
Notice that you do not have to satisfy .
输入格式#
Each test contains multiple test cases. The first line contains an integer ( — the number of test cases.
The first line of each test case contains one integer ( ) — the number of points.
The second line contains integers ( ) — the numbers on the axis.
It's guaranteed that the sum of does not exceed .
输出格式#
For each test case, print a line containing a single integer — the number of distinct pairs with which the game ends.
样例 #1#
样例输入 #1#
9 1 0 2 -1 0 2 1 -1 2 1 1 3 -1 -2 -1 3 1 -2 -1 4 -1 4 -2 1 5 1 1 1 1 -4 5 1 1 1 1 1
样例输出 #1#
2 8 6 7 20 17 34 30 40
提示#
In the first test case, the pairs with which the game ends are and , corresponding to the routes and . Note that is invalid since when , violates . is also invalid since you will go from to forever.
In the second test case, the pairs are .
In the fourth test case, the pairs are .
这题实际上是一个建图题。原始的图 存在一些边的集合:。我们需要找到的是达到 的位置。
定义 org
代表原始路径,org[i]
代表对应的节点。can
代表除了 org
外可以跳出去的节点的数量。
我们把他们分成两类节点,一类是 org
一类是除了 org
之外的节点。
我们通过 org_ok
来判断原始路径是否能直接跳出去。
如果 org_ok
的话,那么除了 org
外的所有节点都可以任意修改(每个节点都可以改 )。否则,我们不能随意修改。
对于 org
上的节点,我们只能考虑:
- 首先是,任何节点都有 种直接跳出去的方式。
- 除此之外,考虑如果
org_ok
可以直接跳到后面 - 最重要的是,我们得考虑
can
中是否包含一些在org_ok
情况下,跳回org
路径的情况,那么我们需要避免修改一些路径导致回路。比如2 -> 4 -> 3 -> -5
并且存在x -> 2
的情况,如果你修改4->x
看起来好像你跳到了一个可以出去的路径,实际上跑的是一个环。所以我们需要预处理in
pa
和preSum
用来剔除这些(其实实现略麻烦)
最后看代码吧,WA
了 N 发。
#include <bits/stdc++.h>
using namespace std;
#define DEBUG 0
// #define int long long
#define pb push_back
#define vt std::vector
#define rep(i, l, r) for (ll i = (l); i < (r); ++ i)
#define per(i, l, r) for (ll i = (l); i >= (r); -- i)
using ll = long long;
using pii = pair<int, int>;
template<typename... Args>
inline void _wpr_(Args... args) { std::cout << '\n'; }
template<typename T, typename... Args>
inline void _wpr_(T val, Args... args) { std::cout << " " << val; _wpr_(args...); }
template<typename T, typename... Args>
void wpr(T val, Args... args) { std::cout << val; _wpr_(args...); }
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
vt<int> global_ans;
void solve(int th){
int n;
std::cin >> n;
vt<int> a(n + 1);
rep (i, 1, n + 1)
std::cin >> a[i];
vt<int> vis(n + 1, 0), org(n + 1, 0), f(n + 1, -1), pa(n + 1, 0), in(n + 1, 0);
iota(all(pa), 0); // init 每个位置等于自己
int cnt = 0;
function<int(int, int)> dfs = [&](int cur, int set_org){
++ cnt;
if (set_org)
org[cur] = 1;
int flag = 1, nxt = cur + a[cur];
if (nxt >= 1 && nxt <= n){
if (!vis[nxt]){
vis[nxt] = 1;
flag = dfs(nxt, set_org);
if (!set_org) pa[cur] = pa[nxt]; // 更新前缀
return f[cur] = flag;
}
else {
if (f[nxt] == -1) return f[cur] = 0; // cycle
// 一定不是第一次 set_org why? 因为第一次要么时 vis[nxt] = 0 或者 找到了 cycle
flag = f[nxt]; // 之前已经计算过了
if (org[pa[nxt]]){ // 如果 nxt 是连接到原始路线上的,我们就需要计算这个前缀
pa[cur] = pa[nxt]; // 更新
in[pa[cur]] += cnt; // 把整个路径上的节点都加到原始节点上
}
}
}
return f[cur] = flag;
};
int org_ok = 0; // 是否有原始路线
int can = 0; // 除了原始路线之外,可以出去的节点数
ll ans = 0, base = 0; // base: 原始路线的长度
rep (i, 1, n + 1){
if (i == 1){
cnt = 0;
vis[i] = 1;
org_ok = dfs(i, 1);
base = cnt;
}else if (vis[i] == 0){
cnt = 0;
vis[i] = 1;
int flag = dfs(i, 0);
if (flag == 1) can += cnt;
}
}
if (org_ok) // 如果有原始路线,那么除了原始路线之外的节点都可以随意设置
ans += (n - base) * (2 * n + 1);
ll preSum = 0; // 计算到当前原始路径点 cr 时,有多少 can 里面的节点是跳到 [1 -> cr] 的路径上,这些点是不能用的!
for (int cr = 1, i = 0; i < base; ++ i){
ans += n + 1; // direct out (每个点都有 n + 1 种方法 直接出去)
preSum += in[cr];
// 这里是计算,借助不在原始路线上的节点,进行跳出的方法数
if (org_ok) // 如果有原始路线,那么我们需要考虑 preSum 对 can 对影响
ans += max(0ll, can - preSum);
else ans += can; // 否则,所有的 can 都可以出去
// 如果原始路线可以跳出,那么可以从 cr 节点直接往后面原始路径上的节点跳
if (org_ok){
ans += base - i - 1; // to nxt
}
cr = cr + a[cr]; // 更新 cr
}
wpr(ans);
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
global_ans.clear();
std::cin >> t;
for (int th = 1; th <= t; ++ th)
solve(th);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人