2021杭电多校第10场

C Pty loves lines

比赛的时候发现有很多区间是连续的,于是先打表看看。

n=699时的最后两个区间为[ 31098 , 31103 ] , [ 31105 , 243951 ]

n=700时的最后两个区间为[ 31145 , 31150 ] , [ 31152 , 244650 ]

观察得到31152之后的区间都是连续的,所以我们只要求出31152之前的每个交点数能不能存在即可。另dp[i][j]表示为 i 条直线是否可以组成 j 个交点。用bitset进行优化,时间复杂度为31152 * n * n / 32

AC Code

#include<bits/stdc++.h>
using namespace std;
const int N=31152;
bitset<N>f[701];
int main()
{
    int t;
    scanf("%d",&t);
    f[0][0]=1;
    for(int i=1;i<=700;i++)
    {
        for(int j=1;j<=i;j++)
            f[i]|=f[i-j]<<(j*(i-j));
    }
    while(t--)
    {
        int n;
        scanf("%d",&n);
        int maxn=min(N-1,n*(n-1)/2);
        printf("%d",0);
        for(int i=1;i<=maxn;i++) if(f[n][i]) printf(" %d",i);
        for(int i=N;i<=n*(n-1)/2;i++) printf(" %d",i);
        printf("\n");
    }
    //system("pause");
    return 0;
}

H. Pty loves string(补)

看到是前缀和后缀组成的串出现的次数首先想到的就是正反做两次kmp,然后就不知道该怎么做了。。。。。那么考虑一个出现位置S[l…r],会满足S[l…l+x-1]和S[1…x]相等,S[r-y+1…r]和S[n-y+1…n]相等,且l+x=r-y+1。以前缀为例,假设考虑当前位置为idx,那么第一个满足要求的前缀即为S[1…idx],然后是S[1…next[ idx ] ] ,然后是S[1…next [ next [ idx ] ] ] 。显然我们要用到border,当前点向该点的border连一条边,我们就得到了一棵树。正反做两次我们就得到了两棵树。

所以现在问题就转化成了给你两棵树,树上每个点都有一个值,然后每次询问给出的x和y两个子树,问两棵子树内有多少个点对(a,b)满足a+1=b。那么我们可以利用dfs序将树上问题转化为序列的区间问题。关键在于两棵树的形状不同,我们怎么将两边的值对应起来。我们可以先按照第一棵树的dfs序建主席树,然后每个dfs序 i 的权值对应着ans[i]+1 (ans[i]表示dfs序i对应的原来的值是多少) 在第二棵树上的dfs序。问题就转化成了在主席树上区间[la,ra]中查询范围为[lb,rb]的数的数量是多少。

AC Code

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
char str[N];
int n,q;
vector<int>g1[N],g2[N];
int nex1[N],nex2[N];
int dfn1[N],l1[N],r1[N],dfn2[N],l2[N],r2[N],ans[N];
int tot1,tot2,cnt,root[N];
struct node
{
    int L,R;    
    int sum=0;    
}tr[N*20];
void kmp1()
{
    nex1[1]=0;
    for(int i=2;i<=n;i++)
    {
        int j=nex1[i-1];
        while(str[j+1]!=str[i]&&j>0) j=nex1[j];
        if(str[j+1]==str[i]) nex1[i]=j+1;
        else nex1[i]=0;
    }
    for(int i=1;i<=n;i++) g1[nex1[i]].push_back(i);
}
void kmp2()
{
    nex2[n]=n+1;
    for(int i=n-1;i>=1;i--)
    {
        int j=nex2[i+1];
        while(str[j-1]!=str[i]&&j<=n) j=nex2[j];
        if(str[j-1]==str[i]) nex2[i]=j-1;
        else nex2[i]=n+1;
    }
    for(int i=1;i<=n;i++) g2[nex2[i]].push_back(i);
}
void dfs1(int x)
{
    dfn1[x]=++tot1;
    ans[tot1]=x;
    l1[x]=tot1;
    for(int i=0;i<g1[x].size();i++)
    {
        int to=g1[x][i];
        dfs1(to);
    }
    r1[x]=tot1;
}
void dfs2(int x)
{
    dfn2[x]=++tot2;
    l2[x]=tot2;
    for(int i=0;i<g2[x].size();i++)
    {
        int to=g2[x][i];
        dfs2(to);
    }
    r2[x]=tot2;
}
void init()
{
    for(int i=0;i<=n+1;i++) g1[i].clear(),g2[i].clear();
    tot1=0,tot2=0;
    cnt=1;
    root[0]=0;
    tr[0].L=tr[0].R=tr[0].sum=0;
}
void update(int num,int &rt,int l,int r)
{
    tr[cnt++]=tr[rt];
    rt=cnt-1;
    tr[rt].sum++;
    if(l==r) return ;
    int mid=(l+r)/2;
    if(num<=mid) update(num,tr[rt].L,l,mid);
    else update(num,tr[rt].R,mid+1,r);
}
int query(int a,int b,int x,int y,int dl,int dr)
{
    if(dl>=x&&dr<=y) return tr[b].sum-tr[a].sum;
    int mid=(dl+dr)/2;
    int res=0;
    if(x<=mid) res=query(tr[a].L,tr[b].L,x,y,dl,mid);
    if(y>mid) res+=query(tr[a].R,tr[b].R,x,y,mid+1,dr);
    return res;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&q);
        scanf("%s",str+1);
        init();
        kmp1(),kmp2();
        dfs1(0);
        dfs2(n+1);
        for(int i=1;i<=n+1;i++)
        {
            root[i]=root[i-1];
            update(dfn2[ans[i]+1],root[i],1,n+1);
        }
        while(q--)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            printf("%d\n",query(root[l1[x]-1],root[r1[x]],l2[n-y+1],r2[n-y+1],1,n+1));
        }
    }
    //system("pause");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值