首页 > 其他分享 >CF1931F Chat Screenshots 另一种题解

CF1931F Chat Screenshots 另一种题解

时间:2024-02-14 15:55:41浏览次数:26  
标签:pre 字符 Chat 题解 偏序 开头 include Screenshots MOD

题目链接:CF 或者 洛谷

本题拓扑排序不再赘述,来说说字符串哈希怎么做这题。

本篇以另一种角度剖析题目背景,并不追求最优,例如有些地方其实可以暴力判断,主要以构造的角度阐述,顺便感谢灵茶山的朋友的讨论。

结论

三个串及其以上必定能构造出最初的那个串。

下面进行证明

首先一个串,显然有多种可能,我们从两个串的构造开始阐述。

对于两个串而言,我们可以轻松构造出反例,如果开头的两个字符在初始串中处于相邻位置,那么很显然无论把谁提到开头,都无法确定出偏序关系,并不知道这两个字符谁前谁后。但很显然这两个字符并不会影响其他的偏序关系,我们可以在两个串中分别去掉这两个字符,然后直接比对剩余串。例如:

1 2 3 4
2 3 1 4,去掉 \(1\) 和 \(2\),都能得到 \(3\ 4\),显然可构造的。如果开头两个字符并不相邻,我们可以寻找第一个串开头在第二个串中的 \(pre\) 或者 \(nxt\),至少有一对相同从而确定正确位置。其实我们这种可以直接双指针判断余留子串或者字符串哈希判断。

三个串的构造

这里说说一种基于偏序关系的构造方式,当然肯定还有其他的方式。

首先一个显而易见的结论,对于任何一个模式串来说,第一个字符或者第二个字符一定是初始串的开头位置。

证明如下

如果我们所提到开头的字符即为初始串开头字符,那么结论成立。如果非开头字符,则原先的开头字符顺位成为第二个字符。而我们在任意一个串中去寻找初始串只需要看两件事:

  1. 找到前两个开头字符哪个是初始开头。

  2. 将另一个非初始开头移至正确位置。

第一个问题,我们只需要确定前两个字符的偏序关系即可。这个至多需要两个串。如果第二个字符,恰好为第二个串的开头,即例如:第一个串开头为 \(1\ 2\),\(2\ 1\),这种显然是无法确定二者偏序的,所以至第三个串不存在这两个字符的开头,剩余的偏序关系是对的。

这里要重新强调一点:提取字符到开头仅仅影响开头字符与其他字符的偏序,而不会影响其他字符之间的偏序,后文中会反复使用。

显然我们只需要找到开头字符不为第二个字符的串即可,抛开第一个待判断串以外,我们至多需要两个串即可找到二者偏序关系。

第一点找到以后,如果恰好开头字符即为初始字符串的开头字符,显然我们是将 开头提到了开头,第一个串本身其实就是初始串。

否则第二个字符是开头字符,我们只需要找到第一个字符的原来位置即可。

下面阐述我个人的寻找方式,首先找到在两个串当中这个需要待插入的字符的 \(pre\),比较他们的 \(pre\)。

  1. 相同说明,这个 \(pre\) 就是正确的了,我们在插入时访问到 \(pre\) 就插入这个字符即可。

  2. 不同说明,原位置前有某个字符被移动到了开头。这两个串当中不同的 \(pre\) 一定有一个是真正的 \(pre\),那个不是真正的 \(pre\) 的仅仅是因为被移到了开头,我们只需要由第一个串反过来确定第二个和第三个串的 \(pre\) 的偏序关系,后面的那个显然即为真正的 \(pre\)。

证明如下:

第一种情况很好证明,说明 \(pre\) 并未受到影响,因为一个字符位置收到影响,仅有可能被提到了开头,而又因为这些串题目给到了开头都互不相同(作者互不相同)。所以至多一个串受影响改变,不可能同时变化。

第二种情况也很好证明,我们记真正的 \(pre\) 前的字符为 \(Pre\),不同的原因很简单,即 \(pre\) 变成了开头,\(Pre\) 成为了其中一个串的 \(pre\),而它们的偏序关系即为 \(pre\) 在 \(Pre\) 之后,又因为 \(pre\) 出现在了开头,所以第一个串的开头不可能为 \(pre\),所以第一个串的二者偏序关系是正确的,通过第一个串找到二者偏序关系即可得到真正的 \(pre\) 构造出初始串。

解法

当拿到初始串以后,我们可以预处理出前缀字符串哈希以及每个数的位置。我们遍历每个待判断串,我们去掉开头剩余的子串的哈希应该是等价于初始串去掉该字符拼出来的子串哈希相等。

例如初始串是 \(2\ 3 \ 4\ 1\),待判断为 \(3\ 2\ 4\ 1\),应该有 \(2+41=241\)。稍微处理下逆元即可。

参照代码
#include <bits/stdc++.h>

// #pragma GCC optimize(2)
// #pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
// #pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")

// #define isPbdsFile

#ifdef isPbdsFile

#include <bits/extc++.h>

#else

#include <ext/pb_ds/priority_queue.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/trie_policy.hpp>
#include <ext/pb_ds/tag_and_trait.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/list_update_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/exception.hpp>
#include <ext/rope>

#endif

using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef tuple<int, int, int> tii;
typedef tuple<ll, ll, ll> tll;
typedef unsigned int ui;
typedef unsigned long long ull;
typedef __int128 i128;
#define hash1 unordered_map
#define hash2 gp_hash_table
#define hash3 cc_hash_table
#define stdHeap std::priority_queue
#define pbdsHeap __gnu_pbds::priority_queue
#define sortArr(a, n) sort(a+1,a+n+1)
#define all(v) v.begin(),v.end()
#define yes cout<<"YES"
#define no cout<<"NO"
#define Spider ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define MyFile freopen("..\\input.txt", "r", stdin),freopen("..\\output.txt", "w", stdout);
#define forn(i, a, b) for(int i = a; i <= b; i++)
#define forv(i, a, b) for(int i=a;i>=b;i--)
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define endl '\n'
//用于Miller-Rabin
[[maybe_unused]] static int Prime_Number[13] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};

template <typename T>
int disc(T* a, int n)
{
    return unique(a + 1, a + n + 1) - (a + 1);
}

template <typename T>
T lowBit(T x)
{
    return x & -x;
}

template <typename T>
T Rand(T l, T r)
{
    static mt19937 Rand(time(nullptr));
    uniform_int_distribution<T> dis(l, r);
    return dis(Rand);
}

template <typename T1, typename T2>
T1 modt(T1 a, T2 b)
{
    return (a % b + b) % b;
}

template <typename T1, typename T2, typename T3>
T1 qPow(T1 a, T2 b, T3 c)
{
    a %= c;
    T1 ans = 1;
    for (; b; b >>= 1, (a *= a) %= c)if (b & 1)(ans *= a) %= c;
    return modt(ans, c);
}

template <typename T>
void read(T& x)
{
    x = 0;
    T sign = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')sign = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    x *= sign;
}

template <typename T, typename... U>
void read(T& x, U&... y)
{
    read(x);
    read(y...);
}

template <typename T>
void write(T x)
{
    if (typeid(x) == typeid(char))return;
    if (x < 0)x = -x, putchar('-');
    if (x > 9)write(x / 10);
    putchar(x % 10 ^ 48);
}

template <typename C, typename T, typename... U>
void write(C c, T x, U... y)
{
    write(x), putchar(c);
    write(c, y...);
}


template <typename T11, typename T22, typename T33>
struct T3
{
    T11 one;
    T22 tow;
    T33 three;

    bool operator<(const T3 other) const
    {
        if (one == other.one)
        {
            if (tow == other.tow)return three < other.three;
            return tow < other.tow;
        }
        return one < other.one;
    }

    T3() { one = tow = three = 0; }

    T3(T11 one, T22 tow, T33 three) : one(one), tow(tow), three(three)
    {
    }
};

template <typename T1, typename T2>
void uMax(T1& x, T2 y)
{
    if (x < y)x = y;
}

template <typename T1, typename T2>
void uMin(T1& x, T2 y)
{
    if (x > y)x = y;
}

constexpr ll N = 2e5 + 10;
ll pw[N];
ll m;
int n;
constexpr ll MOD = 1e9 + 7;
constexpr ll BASE = 33;
ll Inv = qPow(BASE, MOD - 2, MOD);
int start[N];

inline void solve()
{
    cin >> n >> m;
    vector a(m, vector<int>(n));
    for (auto& x : a)for (auto& y : x)cin >> y;
    if (m == 1)
    {
        yes << endl;
        return;
    }
    const int pre1 = a[0][0], pre2 = a[1][0], pre3 = a[0][1];
    if (m == 2)
    {
        //也可以直接双指针判断
        ll x = 0, y = 0;
        int idx1 = 0, idx2 = 0;
        forn(i, 0, n-1)
        {
            if (a[0][i] != pre1 and a[0][i] != pre2)x = (x + a[0][i] * pw[idx1++] % MOD) % MOD;
            if (a[1][i] != pre1 and a[1][i] != pre2)y = (y + a[1][i] * pw[idx2++] % MOD) % MOD;
        }
        cout << (x == y ? "YES" : "NO") << endl;
        return;
    }
    //判断哪个串是开头不为第二个字符
    const int t = a[1][0] == pre3 ? 2 : 1;
    int isPre = 0;
    forn(i, 1, n-1)
    {
        //找偏序关系靠前的那个为开头字符
        if (a[t][i] == pre1 or a[t][i] == pre3)
        {
            isPre = a[t][i];
            break;
        }
    }
    int idx = 0;
    if (isPre == pre1)
    {
        //本身就是初始串
        forn(i, 0, n-1)start[++idx] = a[0][i];
    }
    else
    {
        //找待插入位置的pre
        int preVal = 0;
        forn(i, 1, n-1)
        {
            if (a[1][i] == pre1)
            {
                preVal = a[1][i - 1];
                break;
            }
        }
        forn(i, 1, n-1)
        {
            if (a[2][i] == pre1)
            {
                if (a[2][i - 1] != preVal)
                {
                    //相等就是本身
                    //二者的pre如果不等,通过第一个串的偏序关系确认,靠后的才是真正的pre
                    forv(ans, n-1, 1)
                    {
                        if (a[0][ans] == preVal or a[0][ans] == a[2][i - 1])
                        {
                            preVal = a[0][ans];
                            break;
                        }
                    }
                }
                break;
            }
        }
        forn(i, 0, n-1)
        {
            if (a[0][i] == pre1)continue;
            start[++idx] = a[0][i];
            if (a[0][i] == preVal)start[++idx] = pre1; //找到pre就顺带插入
        }
    }
    vector<ll> pre(n + 1), pos(n + 1);
    forn(i, 1, n)pre[i] = (pre[i - 1] + start[i] * pw[i] % MOD) % MOD, pos[start[i]] = i;
    forn(i, 0, m-1)
    {
        ll t1 = 0;
        forn(j, 1, n-1)t1 = (t1 + a[i][j] * pw[j] % MOD) % MOD; //直接考虑除开开头串的剩余串哈希
        const int id = pos[a[i][0]]; //分割点位置
        ll t2 = (pre[id - 1] + modt(pre[n] - pre[id], MOD) * Inv % MOD) % MOD; //拼串记得有除法需要逆元
        if (t1 != t2)
        {
            no << endl;
            return;
        }
    }
    yes << endl;
}

signed int main()
{
    // MyFile
    Spider
    //------------------------------------------------------
    // clock_t start = clock();
    int test = 1;
    //    read(test);
    cin >> test;
    pw[0] = 1;
    forn(i, 1, N-1)pw[i] = pw[i - 1] * BASE % MOD;
    forn(i, 1, test)solve();
    //    while (cin >> n, n)solve();
    //    while (cin >> test)solve();
    // clock_t end = clock();
    // cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
}

\[时间复杂度为:\ O(nk) \]

标签:pre,字符,Chat,题解,偏序,开头,include,Screenshots,MOD
From: https://www.cnblogs.com/Athanasy/p/18015247

相关文章

  • P8683题解
    首先,看题目描述,给定N个加号,与M个减号,要你求后缀表达式最大值,实际上就是求这些符号和数字排列起来的最大值。这题很容易让人想到贪心。但是呢,又怎么个贪心法呢?很容易看出来,我们可以先用sort进行这么一个排序,之后,我们对于前N大的数加起来,对于剩下的数就减去,于是代码大体就......
  • 麦肯锡问题解决流程-为希望提升水平的产品经理量身定制
    您是否想知道世界上最成功的产品经理如何始终如一地提供不仅满足而且超出预期的解决方案?秘密可能就在于世界上最负盛名的咨询公司之一麦肯锡公司所磨练的方法论。本文深入探讨了麦肯锡的问题解决流程,该流程专为希望提升水平的产品经理量身定制。01.麦肯锡方法:产品管理风......
  • SP34020 ADAPET - Ada and Pet 题解
    题目传送门前置知识前缀函数与KMP算法解法经检验样例,我们发现\(|S|k\)并不是最优答案。考虑利用luoguP4391[BOI2009]RadioTransmission无线传输结论的逆命题,首先必须要有一个完整的\(S\),然后将\(|S|-next_{S}\)复制\(k-1\)次即可。故\(|S|+(|S|-next_{|S|}......
  • P5350 序列 题解
    题目链接:P5350序列比较不难的可持久化文艺平衡树?有道弱化版:数组操作不过弱化版没有随机数据,很显然ODT会直接被卡,这题数据随机倒是能用ODT做一下,而且码量也比较小,可以自己写写,或者参照我给的弱化版我给了这题部分操作的ODT写法。我们还是来讲最实用的可持久化文艺平衡树......
  • UVA12467 Secret Word 题解
    题目传送门前置知识前缀函数与KMP算法解法考虑将\(S\)翻转后得到\(S'\),然后就转化为求\(S'\)的一个最长子串使得其是\(S\)的前缀。使用KMP求解即可。代码#include<bits/stdc++.h>usingnamespacestd;#definelllonglong#defineullunsignedlonglong#d......
  • WGOI R1 真夏飞焰 题解.18014664
    【题目大意】给定序列\(a\),我们定义序列\(a,b\)是「\(k\)相似」的,当且仅当对于\(a\)中每一个四元组\((l_1,r_1,l_2,r_2)\),若满足\(r_1-l_1+1=r_2-l_2+1\lek,l_2=r_1+1,a_{l_1\ldotsr_1}=a_{l_2\ldotsr_2}\),则有\(b_{l_1\ldotsr_1}=b_{l_2\ldot......
  • CF609D题解
    Gadgetsfordollarsandpounds题目传送门题解给一个单\(\log\)题解。“求最早完成买\(k\)样东西的天数。”——很明显的二分答案。在检验函数中,我们应当把前\(k\)小的物品费用之和与总金额作比较,其它题解大多使用直接排序的方法,于是就多了一只\(\log\)。其实没有必......
  • P4559 [JSOI2018] 列队 题解
    题目链接:列队半年前mark的题,结果现在一下子就会做了。顺便写写我的手玩过程和复杂度说明。考虑比较特殊的情况:比较特殊的,发现从黑色到红色区间我们无论咋选择,由于\(\left|a_{right}-a_{left}\right|\),这玩意如果\(right\)表示红色的一边,那么这个绝对值可以直接拆掉,那么......
  • [ARC171E] Rookhopper's Tour 题解
    题目链接首先把\(m=2\)和\(m\)为奇数的情况判掉,由于我们要对合法的摆放方案计数,而一个摆放方案要判断合法性就必须通过一组合法的移动过程,对移动的状态进行记录以此转移和优化显然没啥前途,因此我们考虑摆放方案和移动过程之间的联系。一个比较显然的观察是摆放方案和移动过......
  • 2024牛客寒假算法基础集训营2个人补题题解(K、D)
    比赛链接:2024牛客寒假算法基础集训营2K、TokitsukazeandPassword(easy)题面看着很难实际上只要暴力的东西,赛时看了眼题面就溜了血亏爆搜,枚举\(abcd\)和_可能的值,枚举的情况只有\(9*8*7*6*9=27216\)种。判断按照枚举出的对应值排列出的密码是否满足条件,满足就\(ans++\)写完......