题解:CF1893A 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,否则不能建边。

发现每个节点最多只有一条出边,是内向基环树森林,于是考虑倍增预处理,然后从状态 1k 条边,看看会不会走到尽头即可。

时间复杂度 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;
}