题目大意:有n个点,每个点都指向它后面的第ai个点,现可以任选一个点,然后赋予一个新的ai的值,使从第1个点开始最终能走到编号小于1或大于n的点,问可行的方案数是多少
1<=n<=2e5;-n<=ai<=n
思路:首先,我们可以把第n+1个点作为终点,然后将所有指向编号小于1和大于n的点都指向终点,可以发现,建出来的图是一棵单向树,所有点都向根节点的方向指,所以要遍历它,我们需要反向建边,这样的话我们的目标就是要从终点出发走到1号点。
然后我们发现,如果在不做任何修改的情况下就能从1到终点,非法的方案数是很容易统计的,在从1到终点的这条路径上,对于每一个点,将指向终点方向的那条边指向自己或前驱节点,就会构成环,无法到达终点,同时在图中除了终点和1号点构成的树,还有其他的含环的连通分量,连接这些连通分量中的任意点也无法到达终点,所以要求出路径上每个点的前驱节点,在反向建边的情况下,就是求子节点数,用dfs即可简单求出,其他连通分量的点数就用n-树的大小即可
如果在不做修改的情况下,从1无法到达终点,那么可行的方案就是将1所连的连通分量的点连接到终点所在的连通分量上,同时要注意每个点都有n+1种方案是可以直接连到终点上的,按此法从1开始统计到 出现循环即可
//#include<__msvc_all_public_headers.hpp>
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
typedef long long ll;
int head[N];
struct Edge
{
int v, next;
}e[N];//链式前向星存图
int tot = 0;
void addedge(int u, int v)
{
e[++tot].v = v;
e[tot].next = head[u];
head[u] = tot;
}
ll n;
bool vis[N];
bool vis2[N];
void init()
{
for (int i = 1; i <= n + 1; i++)
{
head[i] = -1;
vis[i] = 0;
vis2[i] = 0;
}
tot = 0;
}
ll pre[N];
int ne[N];
void dfs(int u)
{
pre[u] = 1;
for (int i = head[u]; ~i; i = e[i].next)
{
int v = e[i].v;
if (vis[v])
{
continue;
}
vis[v] = 1;
dfs(v);
pre[u] += pre[v];//记录当前图的后继节点数,也就是原图的前驱节点数
}
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
int t;
cin >> t;
while (t--)
{
cin >> n;
init();
for (int i = 1; i <= n; i++)
{
int v;
cin >> v;
ne[i] = i + v;//记录每个点实际指向的节点
if (i + v < 1 || i + v > n)
{//终点同意记为n+1
ne[i] = n + 1;
}
addedge(ne[i], i);//反向建边
}
dfs(n + 1);//从终点开始遍历
pre[n + 1]--;//终点不计入树的大小
ll ans = 0;//记录方案数
if (vis[1])
{//终点和11号点联通
ans = (2 * n + 1) * n;//最大方案数
int cur = 1;
while (cur != n + 1)
{//遍历从1到终点的路径
ans -= pre[cur] + n - pre[n + 1];//当前点的前驱节点数和图中树外节点数
cur = ne[cur];
}
cout << ans << endl;
continue;
}
int cur = 1;
while (!vis2[cur])
{//从1开始走知道成环
ans += n + 1 + pre[n + 1];//终点所在的连通分量的大小+重合的终点数
vis2[cur] = 1;
cur = ne[cur];
}
cout << ans << endl;
}
return 0;
}