2019牛客第十场题解(C/F)

17 篇文章 0 订阅
3 篇文章 0 订阅

C

题意:第i天在原来字符串的基础上在字符串的前面或者后面添加一个字符。问添加完字符后有多少种不同长度的循环节。

定义循环节为能把原字符串分割成长度为k的一个或多个部分。并且后面可以有或者没有循环节的开头部分。

也就是说 s[1]~s[n-x] == s[x+1]~ s[n] 则说明循环节为x 。

做法一 hash+二分:

1.明显,对于长度为len的字符串,最少有一个长度为len的循环节。

2.答案单调性,如果一个长度为r的字符串有长度为x的循环节。那么长度x~r的所有字符串都有长度为x的循环节(手动模拟下可以证明,将原字符串不断删去开头或者末尾的字符必然还是满足s[1]~s[n-x] == s[x+1]~ s[n])

同理如果长度为r的字符串没有长度为x的循环节,那么长度大于r的字符串也一定没有长度为x的循环节(同样手动模拟在字符串后面或者前面添加字符)

那么我们可以想到,对长度为i=(1~n)的循环节我们在原字符串的第i~第n阶段 进行二分。找到拥有循环节i 的最大字符串长度r

那么i~r 都有循环节i, 结果加1 那么我们可以通过前缀和的方式将区间修改 变成单点修改。让a[i]++, a[r+1]--  则前缀和a[1]+...a[i]为

第i个阶段的字符串的循环节的种类数。

为了对字符串二分。我们先把最终阶段的字符串hash,然后记录一下每次变化后字符串的左端点位置和右端点位置。

(具体做法是把head 设置为maxlen+1 ,tail 设置为maxlen+2 然后head往前减 tail往后加 最后把L[i]-=head, R[i]-=head则可以得到每个阶段的左右端点)

然后每次二分check看L[i]~R[i]-x 是否等于x+1~R[i] 来判断第i 阶段可不可行。

总复杂度O(nlog(n))

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int maxn=2e6+6;
int n;
char s[10];
char ss[10];
int L[maxn],R[maxn],a[maxn];
struct Hash
{
    ull ha[maxn],p[maxn],base=233;
    void get_hash(char *buf)
    {
        int n=strlen(buf);
        p[0]=1;
        for(int i=1;i<=n;i++)p[i]=p[i-1]*base;
        for(int i=1;i<=n;i++)
        {
            ha[i]=(ha[i-1]*base+(buf[i-1]+1));
        }
    }
    ull query(int l,int r)
    {
       ull ans=ha[r]-ha[l-1]*p[r-l+1];
       return ans;
    }
}hh;
bool check(int q,int x)
{
    return hh.query(L[q],R[q]-x)==hh.query(x+L[q],R[q]);
}
int main()
{
    scanf("%d",&n);
    int head=1e6+1,tail=1e6+2;
    char t[maxn];
    for(int i=1;i<=n;i++)
    {
        scanf("%s%s",s,ss);
        char ch=ss[0];
        if(ch=='s'&&ss[1]=='i')ch='z';
        if(s[0]=='p')t[head--]=ch;
        else t[tail++]=ch;
        L[i]=head+1;
        R[i]=tail-1;
    }
    hh.get_hash(t+head+1);
    for(int i=1;i<=n;i++)L[i]-=head,R[i]-=head;
    for(int i=1;i<=n;i++)
    {
        int l=i,r=n;
        int mid;
        while(l<=r)
        {
            mid=(l+r)>>1;
            if(check(mid,i))l=mid+1;
            else r=mid-1;
        }
        a[r+1]--;
        a[i]++;
    }
    for(int i=1;i<=n;i++)
    {
        a[i]+=a[i-1];
        printf("%d\n",a[i]);
    }
    return 0;
}

F

题意:给你n个点,坐标范围为1e5 ,选不超过3条的横线和不超过3条的竖线。相邻线的距离固定为r,求位于这些线上的点的数量的最大值。

做法:用一棵线段树维护值域上 第i, i+r, i+r*2 行上点的总数。 也就是说对于b[i] = a[i]+a[i+r]+a[i+r*2] 然后询问整个区间的最大值。

然后对于列,我们用一个值域上的vector 存储每列上对应的x的值。然后我们 每次取出 第 i,i+r,i+r*2 列,然后将这些列上的x 在线段树的对应点处删除。 则 ans更新为 三列的元素个数加上 线段树的区间最大值。 每次更新完答案我们再把删除的x加回去。  

(对于一个x  他是 x,x-r , x-r*2 节点中的值, 所以这三个节点 需要变化)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=100001;
int a[100005];
int b[100005];
int n,r;
vector<int>v[100005];
struct segment_tree
{
    int l,r;
    int mx;
}t[maxn*4];
void pushup(int o)
{
    t[o].mx=max(t[o<<1].mx,t[o<<1|1].mx);
}
void build(int o,int l, int r)
{
    t[o].l=l; t[o].r=r;
    if(l==r)
    {
        t[o].mx=b[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(o<<1,l,mid);
    build(o<<1|1,mid+1,r);
    pushup(o);
}
void change(int o,int x,int d) // 单点更新
{
    if(t[o].l==t[o].r)
    {
        t[o].mx+=d;
        return ;
    }
    int mid=(t[o].l+t[o].r)>>1;
    if(x<=mid)change(o<<1, x, d);
    else change(o<<1|1, x, d);
    pushup(o);
}

int query(int o, int l, int r)
{
    if(l<=t[o].l&&t[o].r<=r)
    {
        return t[o].mx;
    }
    int mid=(t[o].l+t[o].r)>>1;
    int mx=-1;
    if(l<=mid)mx=max(query(o<<1,l,r),mx);
    if(r>mid)mx=max(query(o<<1|1,l,r),mx);
    return mx;
}
int main()
{
    scanf("%d%d",&n,&r);
    int x,y;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&x,&y);
        a[x+1]++;
        v[y+1].push_back(x+1);
    }
    for(int i=1;i<=maxn;i++)
    {
        b[i]+=a[i];
        if(i+r<=maxn)b[i]+=a[i+r];
        if(i+r+r<=maxn)b[i]+=a[i+r+r];
    }
    build(1,1,maxn);
    int ans=-1;
    for(int i=1;i<=maxn;i++)
    {
        vector<int>tmp;
        for(auto j:v[i])
        {
            tmp.push_back(j);
        }
        if(i+r<=maxn)
        {
            for(auto j:v[i+r])
            {
                tmp.push_back(j);
            }
        }
        if(i+r+r<=maxn)
        {
            for(auto j:v[i+r+r])
            {
                tmp.push_back(j);
            }
        }
        for(auto k:tmp)
        {
            change(1,k,-1);
            if(k-r>=1)change(1,k-r,-1);
            if(k-r*2>=1)change(1,k-r*2,-1);
        }
        ans=max(ans, (int)(tmp.size())+query(1,1,maxn) );
        for(auto k:tmp)
        {
            change(1,k,1);
            if(k-r>=1)change(1,k-r,1);
            if(k-r*2>=1)change(1,k-r*2,1);
        }
    }
    printf("%d\n",ans);
    return 0;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值