[hihoCoder挑战赛24](贪心)(期望DP)(后缀自动机)

这篇博客详细介绍了hihoCoder挑战赛中的三道题目,涉及贪心策略和期望DP解法。在A题中,通过贪心策略寻找字典序最小的排列;B题探讨了字符串子序列期望个数的计算;C题利用后缀自动机解决字符串子串个数问题,复杂度为O(nlogn)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A:Rikka with Sequence IV

题意简述

有一个长度为 n 的数列A,求一个长度为 n 的排列P,满足对于任意的 i[1,n] 都满足: ij=1Apj0
求想要知道满足条件的字典序最小的排列 P

思路

贪心。
从前往后扫,选出第一个前缀和大于0的。
这样扫n遍扫出所有的数。
复杂度O(n2)

代码

#include<cstdio>
using namespace std;
int n,cnt;
long long sum;
int seq[1010],ans[1010];
bool vis[1010];
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%d",&seq[i]);
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=n;j++)
            if (!vis[j]&&sum+seq[j]>=0)
            {
                sum+=seq[j];
                ans[++cnt]=j;
                vis[j]=1;
                break;
            }
        if (cnt!=i)
        {
            printf("Impossible");
            return 0;
        }
    }
    for (int i=1;i<=n;i++)
        printf("%d ",ans[i]);
    return 0;
}

B:Rikka with Subsequence

题意简述

有一个长度为 n 的字符串s,现在他对这个字符串进行了一个操作,在操作之后第 i 个字符有Ai%的概率被删除。现在他想要知道在操作之后得到的新的字符串的本质不同的子序列个数的期望值(包括空串)。
Note:字符串 s 是字符串t的子序列当且仅当删除了 t 中若干个位置之后可以得到字符串s

思路

这个人还没有做过
题解见这个人的题解

代码

C:Rikka with String

题意简述

有一个长度为n的只包含小写字母的字符串s,现在可以选取一个位置 k[1,n] 并把 s[k] 上的字符替换成 #
对每一个 k[1,n] ,求修改后的字符串中本质不同的子串个数。

思路

i 位置的答案为g[i],初值为不替换的不同子串的个数,可以观察到,如果替换 s[i] # ,一定会使得答案增加。
考虑每一种本质不同子串对答案的影响。
某一子串的出现位置为 (lj,rj) ,如果 j i[lj,rj] ,那么就会对 g[i] 贡献1的答案。
我们发现这样太蛋疼了= =
考虑补集转换,对于某一个位置 i ,它变成了一个没有出现过的字符,我们先把所有可能增加的答案,即为跨过这个点的所有子串都加进去,再减去不合法的。
这样我们继续考虑本质不同子串对答案的影响,如果对于 j i[lj,rj] ,它对 g[i] 有-1的贡献。
我们发现这个东西就非常好统计了!
后缀三兄弟皆可搞!
在这里我选用了后缀自动机
我们需要的有四个信息:
1. minx ,right集合中最小的位置。
2. maxx ,right集合中最大的位置。
3. maxlen ,所能接受的最长子串。
4. minlen=premaxlen+1 ,所能接受的最短子串。
可以通过简单的拓扑求出。
子串的贡献区间为 [maxxlen+1,minx]
后缀自动机一个节点表示多个right集合相同的不同长度的子串。
那么一个节点对 [maxxlen+1,maxxminlen] 贡献一个首项为-1,公比为-1的等差数列;
[maxxminlen+1,minx] 贡献 maxlenminlen+1
可以用树状数组实现之,时间复杂度 O(nlogn)
二阶差分可以得到更优的时间复杂度。具体做法是先对 g 差分得到g,再对 g 差分得到 g′′ ,加一个等差数列和区间加一个常数都变为了单点修改。最后做两次前缀和即可得到 g
时间复杂度O(n)

涨姿势……

代码

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
#define INF 0x3f3f3f3f
int n;
long long total;
long long ans[300010];
char st[600010];
namespace SAM
{
    struct Node{
        Node *pre,*next[26];
        int val,minx,maxx;
        Node(){};
        Node(int);
    }*null;
    Node::Node(int _val)
    {
        val=_val;
        for (int i=0;i<26;i++)
            next[i]=null;
        minx=INF;
        maxx=0;
        pre=null;
    }

    Node pool[600010];
    Node *Root,*current;
    int poolt,now;
    int sum[600010],order[600010];
    Node *NewNode(int _val)
    {
        pool[++poolt]=Node(_val);
        return &pool[poolt];
    }
    void Init()
    {
        null=NewNode(0);
        *null=Node(0);
        Root=NewNode(0);
        current=Root;
    }
    void extend(char ch)
    {
        int x=ch-'a';
        Node *p=current;
        Node *np=NewNode(p->val+1);
        while (p!=null&&p->next[x]==null)
            p->next[x]=np,p=p->pre;
        if (p==null)
            np->pre=Root;
        else
        {
            Node *q=p->next[x];
            if (q->val==p->val+1)
                np->pre=q;
            else
            {
                Node *nq=NewNode(p->val+1);
                memcpy(nq->next,q->next,sizeof(q->next));
                nq->pre=q->pre;
                q->pre=nq;
                np->pre=nq;
                while (p!=null&&p->next[x]==q)
                    p->next[x]=nq,p=p->pre;
            }
        }
        total+=np->val-np->pre->val;
        np->maxx=np->val;
        np->minx=np->val;
        current=np;
    }
    void solve()
    {
        for (int i=2;i<=poolt;i++)
            sum[pool[i].val]++;
        for (int i=1;i<=n;i++)
            sum[i]+=sum[i-1];
        for (int i=2;i<=poolt;i++)
            order[sum[pool[i].val]--]=i;
        for (int i=poolt-1;i>=1;i--)
        {
            pool[order[i]].pre->minx=min(pool[order[i]].pre->minx,pool[order[i]].minx);
            pool[order[i]].pre->maxx=max(pool[order[i]].pre->maxx,pool[order[i]].maxx);
        }
        for (int i=poolt-1;i>=1;i--)
            if (pool[order[i]].maxx-pool[order[i]].val+1<=pool[order[i]].minx)
            {
                int l=pool[order[i]].maxx-pool[order[i]].val+1;
                int r=min(pool[order[i]].maxx-(pool[order[i]].pre->val+1)+1,pool[order[i]].minx);
                int pp=r-l+1;
                if (l<=r)
                {
                    ans[l]++;
                    ans[r+1]-=pp+1;
                    ans[r+2]+=pp;
                }
                l=r+1,r=pool[order[i]].minx;
                if (l<=r)
                {
                    ans[l]+=pp;
                    ans[l+1]-=pp;
                    ans[r+1]-=pp;
                    ans[r+2]+=pp;
                }
            }
        for (int i=1;i<=n;i++)
            ans[i]+=ans[i-1];
        for (int i=1;i<=n;i++)
            ans[i]+=ans[i-1];
    }
}

void init()
{
    scanf("%d",&n);
    scanf("%s",st+1);
    SAM::Init();
    for (int i=1;i<=n;i++)
        SAM::extend(st[i]);
}
void work()
{
    SAM::solve();
    for (int i=1;i<=n;i++)
        printf("%lld ",1LL*i*(n-i+1)+total-ans[i]);
}
int main()
{
    init();
    work();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值