# 算法杂记 2023/02/26

## 题面翻译

### 题目描述

$$n$$ 个点，第 $$i$$ 个点上有数字 $$a_i$$，你在这些点上面玩一个游戏，初始在点 $$1$$。当你在点 $$i$$ 时：

• $$1\le i\le n$$，则你会跳到点 $$i+a_i$$
• 否则游戏结束。

## 题目描述

There are $n$ points $1,2,\ldots,n$ , each point $i$ has a number $a_i$ on it. You're playing a game on them. Initially, you are at point $1$ . When you are at point $i$ , take following steps:

• If $1\le i\le n$ , go to $i+a_i$ ,
• Otherwise, the game ends.

Before the game begins, you can choose two integers $x$ and $y$ satisfying $1\le x\le n$ , $-n \le y \le n$ and replace $a_x$ with $y$ (set $a_x := y$ ). Find the number of distinct pairs $(x,y)$ 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 $a_x\not=y$ .

## 输入格式

Each test contains multiple test cases. The first line contains an integer $t$ ( $1\le t\le 10^4)$ — the number of test cases.

The first line of each test case contains one integer $n$ ( $1\le n\le 2\cdot 10^5$ ) — the number of points.

The second line contains $n$ integers $a_1,a_2,\ldots,a_n$ ( $-n \le a_i \le n$ ) — the numbers on the axis.

It's guaranteed that the sum of $n$ does not exceed $2\cdot 10^5$ .

## 输出格式

For each test case, print a line containing a single integer — the number of distinct pairs $(x,y)$ 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 $(x,y)$ with which the game ends are $(1,-1)$ and $(1,1)$ , corresponding to the routes $1\rightarrow 0$ and $1\rightarrow 2$ . Note that $(1,2)$ is invalid since when $n=1$ , $y=2$ violates $-n\le y\le n$ . $(1,0)$ is also invalid since you will go from $1$ to $1$ forever.

In the second test case, the pairs are $(1,-2),(1,-1),(1,2),(2,-2),(2,-1),(2,0),(2,1),(2,2)$ .

In the fourth test case, the pairs are $(1,-2),(1,-1),(1,1),(1,2),(2,-2),(2,1),(2,2)$ .

• 首先是，任何节点都有 $$n+1$$ 种直接跳出去的方式。
• 除此之外，考虑如果 org_ok 可以直接跳到后面
• 最重要的是，我们得考虑 can 中是否包含一些在 org_ok 情况下，跳回 org 路径的情况，那么我们需要避免修改一些路径导致回路。比如 2 -> 4 -> 3 -> -5 并且存在 x -> 2 的情况，如果你修改 4->x 看起来好像你跳到了一个可以出去的路径，实际上跑的是一个环。所以我们需要预处理 in papreSum 用来剔除这些（其实实现略麻烦）

#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;
}

posted @ 2023-02-26 21:53  Last_Whisper  阅读(39)  评论(0编辑  收藏  举报