Atcoder ABC268 题解&总结

rank:479

AC:ABCDF

D. Unique Username

发现最多有 \(8\) 个,那么我们可以直接暴力枚举一下这 \(8\) 个串的全排列,在中间插入几个 _,然后可以选择直接用 set 暴力判重。

E. Chinese Restaurant (Three-Star Version)

考虑一个人的影响,可以发现从那个人开始,是 \(1,2,\dots,\frac n2,\dots,2,1\),所以可以画出大致这样的图像:

ABC268E1.png

为了方便,我们将环复制一遍,这样每个点可以是后面上升与下降的两个函数,一个函数的 \(k\)\(b\) 是不变的,并且具有可加性,所以这样就好弄多了。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

ll b[400100],k[400100];
int p[200100];
ll ans=1e14;

int main()
{
    int n;
    int i;

    cin>>n;
    for(i=0; i<n; i++)
    scanf("%d", &p[i]);

    for(i=0; i<n; i++)
    {
        int x=(p[i]-i+n)%n; // 转到对应位置(不满值为 0)的次数
        // 差分
        b[x]-=x; b[x+n/2+1]+=x;
        k[x]+=1; k[x+n/2+1]-=1;
        b[x+n/2+1]+=x+n; b[n+x]-=x+n;
        k[x+n/2+1]-=1; k[n+x]+=1;
    }

    for(i=0; i<n*2; i++)
    b[i]+=b[i-1],k[i]+=k[i-1];

    for(i=0; i<n; i++)
    ans=min(ans, b[i]+k[i]*i+b[i+n]+k[i+n]*(i+n));
    cout<<ans;
    return 0;
}

F. Best Concatenation

假设每个字符串里面有 \(a\)X,前面加一个 X 的贡献是 \(b\)。首先我们把每个字符串里面的 X 对当前字符串的贡献直接加进去,这部分肯定是不变的,只需要考虑与其它字符串的影响。

假设当前有两个串 \(x\)\(y\),设前面已经有了 \(A\)X,当前答案为 \(ans\),那么如果 \(x\)\(y\) 优,要满足下面的式子:

\[\begin{aligned} ans + A \times b_x + (a_x+A) \times b_y &> ans + A \times b_y + (a_y+A) \times b_x \newline a_x \times b_y &> a_y \times b_x \end{aligned} \]

直接排序即可。

G. Random Student ID

对于一名学生,他的排名是比他小的学生的数量,所以我们只需要找到每名学生小于他的概率即可。

我们分为三种情况来讨论(设当前为 \(x\)

  1. 如果是 \(x\) 的前缀,那么可以知道他一定比 \(x\) 小,那么概率就是 \(1\)
  2. 如果 \(x\) 是当前串的前缀,那么可以知道 \(x\) 一定比他小,概率就是 \(0\)
  3. 否则,证明一定有一位两者不一样,那也就是根据这一位上的两个不同的字母比较大小。因为是全排列都有可能,也就是两个字母要不就是这个大,要不就是这个小,所以概率是 \(0.5\)

如果设有 \(pre\) 个是 \(x\) 的前缀,有 \(nxt\)\(x\) 他们的前缀,那么排名就是

\[pre \times 1 + nxt \times 0 + (n-pre-nxt) \times 0.5 = (n+pre-nxt) \times 0.5 \]

下面介绍一个很简单的 \(O(n)\)\(pre\)\(nxt\)(不得不说太神奇了)

我们首先对每个串后面添加一个大于 z 的字符(例如 |),然后与原串一块放到一个数组,排个序。原串当成左括号,添加后的当成右括号,可以发现两者之间的就是 \(nxt\),而没有闭合的左括号就是 \(pre\)

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll p = 998244353;
const ll inv2 = 499122177;

pair<string,int> a[1000100];
int pre[1000100],nxt[1000100];
int idx[1000100];
int cnt;

int main()
{
    int n;
    int i;

    cin>>n;
    for(i=1; i<=n; i++)
    {
        string s;
        cin>>s;
        a[i]=make_pair(s, i);
        a[i+n]=make_pair(s+'|', i);
    }

    sort(a+1, a+1+n*2);
    for(i=1; i<=2*n; i++)
    {
        string s=a[i].first;
        if(s[s.size()-1]!='|')
        {
            ++cnt;
            idx[a[i].second]=i;
            pre[a[i].second]=cnt;
        }
        else
        {
            --cnt;
            nxt[a[i].second]=(i-idx[a[i].second])/2;
        }
    }

    for(i=1; i<=n; i++)
    printf("%lld\n", (pre[i]+n-nxt[i])*inv2%p);
    return 0;
}

EX. Taboo

题意

给定一个字符串 \(S\),和 \(n\) 个字符串 \(T\),最少需要将多少个 \(s_i\) 变为 * 可以使 \(S\) 没有子串为 \(T\)

分析

首先我们可以发现每个位置可以匹配一些字符串,那么也就是一些区间,我们需要将这些区间用最少的点覆盖掉。那么对于同一个点,如果长度较小的区间被覆盖,那么长度比较大的区间一定也被覆盖。根据这个性质,我们只需要找到每个点最短的那个区间就可以了,也就是以这个点为起点,最短的匹配串。

求出每个点最短的匹配串后,我们可以贪心的选点,对于一个点,肯定越靠后,这个点可能覆盖的区间越多。

那么怎么求每个位置的最短匹配串呢?Atcoder 官方给的是使用后缀数组求,具体方法没怎么研究(主要本人后缀数组比较垃圾)。这里选择使用 AC 自动机去求。我们首先将 \(T\) 构建 AC 自动机,然后匹配 \(S\),如果有结束标记,设这个长度为 \(len_j\),对于每个位置,我们一样跳 \(fail\) 边,然后可以得到:

\[a_{i-len_j+1} = \min_{j=fail_j} (len_j) \]

根本没有什么性质!!!如果暴力跳 \(fail\) 肯定 TLE,所以可以将所有串翻转一下,这是就变成了:

\[a_{n-i+1} = \min_{j=fail_j}(len_j) \]

也就是 \(a_{n-i+1}\) 是失配树上一直到根节点的一段路径的最小值,此时就好维护多了,我们直接存一下每个节点有哪些位置,然后遍历失配树就可以了。最后跑一边贪心直接出答案~

#include<bits/stdc++.h>
using namespace std;

struct ACTrie
{
    int tr[500100][26];
    int fail[500100];
    bool flag[500100];
    int len[500100];
    int cnt=1;
    vector<int> v[500100];
    vector<int> p[500100];
    int *a;
    void insert(char s[])
    {
        int n=strlen(s+1); reverse(s+1, s+1+n);
        int u=1;
        for(int i=1; i<=n; i++)
        {
            int c=s[i]-'a';
            if(!tr[u][c]) tr[u][c]=++cnt;
            u=tr[u][c];
        }
        flag[u]=1; len[u]=n;
    }
    void build()
    {
        queue<int> q;
        for(int i=0; i<26; i++) tr[0][i]=1;
        q.push(1);
        while(q.size())
        {
            int u=q.front(); q.pop();
            for(int i=0; i<26; i++)
            {
                int v=tr[u][i];
                if(!v)
                {
                    tr[u][i]=tr[fail[u]][i];
                    continue;
                }
                fail[v]=tr[fail[u]][i];
                q.push(v);
            }
        }
        for(int i=1; i<=cnt; i++)
        v[fail[i]].push_back(i);
    }
    void search(char s[])
    {
        int n=strlen(s+1); reverse(s+1, s+1+n);
        int u=1;
        for(int i=1; i<=n; i++)
        {
            u=tr[u][s[i]-'a'];
            p[u].push_back(n-i+1);
        }
    }
    void dfs(int x, int k)
    {
        if(flag[x]) k=min(k, len[x]);
        for(int y : p[x])
        a[y]=k;
        for(int y : v[x])
        dfs(y, k);
    }
};

char s[500100],ss[500100];
ACTrie t;
int a[500100];
int ans,minn=1e9;

int main()
{
    int n,m;
    int i;

    scanf("%s%d", s+1, &n); m=strlen(s+1);
    for(int i=1; i<=n; i++)
    {
        scanf("%s", ss+1);
        t.insert(ss);
    }
    
    t.build();
    memset(a, 0x3f, sizeof(a)); t.a=a;
    t.search(s); t.dfs(1, 1e9);
    for(i=1; i<=m; i++) // 贪心
    {
        minn=min(minn, i+a[i]-1);
        if(minn==i)
        ++ans,minn=1e9;
    }
    cout<<ans;
    return 0;
}

至此,ABC268 全面完工!

posted @ 2022-09-14 19:26  淸梣ling  阅读(122)  评论(0)    收藏  举报