Codeforces 1893A Anonymous Informant 题解 [ 绿 ] [ 基环树 ] [ 倍增 ] [ 循环节 ] [ 图论建模 ]
Anonymous Informant:主播主播,你的循环节递推确实很强,但还是太吃思维了,有没有什么好想好写的图论建模推荐一下!有的兄弟,有的,像这样的图论建模还有好多,都是现在 CCF 不考的类型,告诉主播你想学哪个!
Sol.1 基环树倍增
注意到把一个序列循环移动后合法的状态只有 \(n\) 个,考虑把每个状态抽象为一个节点,假设节点 \(i\) 表示以 \(B\) 的第 \(i\) 位为开头的状态。因为需要复原原来的 \(A\) 数组,所以连边就是枚举序列中的每个数,倒着建边:$i\bmod n +1\to (B_{i}-i+n)\bmod n+1 $。其中需要保证 $ 1\le B_i \le n$,否则不能建边。
发现每个节点最多只有一条出边,是内向基环树森林,于是考虑倍增预处理,然后从状态 \(1\) 走 \(k\) 条边,看看会不会走到尽头即可。
时间复杂度 \(O(n\log k)\)。好想好写。或者也可以跑 SCC 缩点之后暴力遍历节点也可以做到 \(O(n)\),但是要写 Tarjan,太难写了。
Sol.2 循环节递推
和上面差不多,只是多了个观察:每次移位后的 \(B_n\) 是上次移位的 \(x\)。于是可以据此推导出上一个状态。因此我们维护一个指针,表示当前序列的结尾在原序列中的位置,然后暴力跳 \(k\) 次即可。如果跳到了原来跳过的位置则说明有循环节,则一定有解,返回即可。若遇到 \(B_p >n\) 的情况,则说明一定无解。
递推公式:\(p\gets (p-B_p+n-1)\bmod n+1\)。
循环节最多长度为 \(n\),所以时间复杂度 \(O(n)\)。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const int N=200005;
ll n,k,a[N];
bitset<N>vis;
void solve()
{
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
int p=n;
vis.reset();
while(k--)
{
if(a[p]>n)
{
cout<<"No\n";
return;
}
if(vis[p]==1)
{
cout<<"Yes\n";
return;
}
vis[p]=1;
p=(p-a[p]+n-1)%n+1;
}
cout<<"Yes\n";
}
int main()
{
//freopen("sample.in","r",stdin);
//freopen("sample.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--)solve();
return 0;
}

浙公网安备 33010602011771号