10.9离线赛

一、双击
数据:对于60%,n、m∈[1,5000]
对于100%,n、m∈[1,500000]

对于60%,N*M的很好想,对于每个询问点,向前一直和向后一直找就行了。
然后可以想到其实对于这一块里所有答案都是一样的,那就可以预处理出来,把每一块的L、R存下来,放在两个数组里。询问时在R数组里二分查找。
再者,可以用并查集,把一块里其他点都指向第一个点,把这一块的L、R坐标放在第一个点上。询问的时候就去找执行的那个点就行了,这样只有N了

#include<bits/stdc++.h>
using namespace std;
void Rd(int &res){
    res=0;char c;
    while(c=getchar(),c<48);
    do res=(res<<3)+(res<<1)+(c&15);
    while(c=getchar(),c>=48);
}
char str[500005];
struct node{int L,R;}A[500005];
int fa[500005];
int Find(int x){return x==fa[x]?x:fa[x]=Find(fa[x]);}
int op(char c){return c>='0'&&c<='9';}
int main(){
    int n,m;
    Rd(n);Rd(m);
    scanf("%s",str);
    for(int i=0;i<n;i++)fa[i]=i;
    int L=0,R=0;
    for(int i=0;i<n;i++){
        if(op(str[i])==op(str[L])){R=i;fa[i]=L;}
        else{//把L、R位置存起来
            A[L].L=L;A[L].R=R;
            L=R=i;
        }
    }
    A[L].L=L;A[L].R=R;//最后一个L、R要单独存
    fa[R]=L;
    for(int i=1;i<=m;i++){
        int x;
        Rd(x);
        int y=Find(x);
        printf("%d %d\n",A[y].L,A[y].R);
    }
    return 0;
}

二、取子串
数据:对于30%,lenS∈[1,10],lenT=1
对于100%,lenS∈[1,1e5],lenT∈[1,1e5]

一看就是dp题,难点有两个。

1)能否快速判断这个字串是不是T
2)怎么转移方案数

对于1,可以Hash一下,把S、T都弄出来,然后见一下就行了,O(N)的预处理。
对于2,要细讲。
定义dp[i]为以i位置为末时的方案数;
每次对于一个i,有两种情况。一个是不和前面的匹配成T,另一个是和前面的合并变成T。
对于第一个,只要每次和前面的所有T合并就行了,相当于把一个字母跟在前面的一个T后面,那就是前面的所有方案又被弄了一次,那dp[i]=dp[i-1]
对于第二个,如果合并成了T,也有两种情况,一种是T自己这一块,有i-m+1的情况(就是把1到i-m的点都依次并到这边来),然后是T和前面的方案数去合并。图示如下:
这里写图片描述
这里的dp是以i为末尾是的情况,不要弄错了

#include<bits/stdc++.h>
#define Mod 1000000007
#define M 100005
#define P 233
using namespace std;
char S[M],T[M];
int n,m,dp[M],cnt[M],sum[M];
unsigned long long H[M],HT,base[M];
bool judge(int x){return H[x]-H[x-m]*base[m]==HT;}
int main(){
    scanf("%s %s",S+1,T+1);
    n=strlen(S+1),m=strlen(T+1);

    base[0]=1;
    for(int i=1;i<=n;i++)base[i]=base[i-1]*P;
    for(int i=1;i<=n;i++)H[i]=H[i-1]*P+S[i]-'a';
    for(int j=1;j<=m;j++)HT=HT*P+T[j]-'a';
    //这一块是hash,字符串匹配,要用unigsed long long,不然会超

    for(int i=1;i<=n;i++){
        if(judge(i)){//第二种情况
            dp[i]=(dp[i]+i-m+1)%Mod;
            //for(int j=1;j<=i-m;j++)dp[i]=(dp[i]+cnt[j])%Mod;
            //这里是不用前缀和的,用来解释sum这个前缀和,前缀和的前缀和
            dp[i]=(dp[i]+sum[i-m])%Mod;
        }
        else dp[i]=dp[i-1];//第一种情况
        cnt[i]=(cnt[i]+cnt[i-1]+dp[i])%Mod;//dp的前缀和
        sum[i]=(sum[i]+sum[i-1]+cnt[i])%Mod;//sum的前缀和
    }
    printf("%d\n",cnt[n]);//输出的是dp[1--n]的和,即cnt[n]
    return 0;
}

三、Paths
数据:对于30%,n、m∈[1,20]
对于100%,n、m∈[1,100000]

对于60%,二进制枚举很简单,把每条路径上每个点标记掉,然后再选。

对于100%,贪心就能过。
对于一条链,把下标重新赋值后,就是看电视一样的贪心。
放到树上呢,其实是一样的。按照LCA从大到小排个序后,每次先选深度大的,然后能选就选。正确性显然,两条路径,若相交,那么肯定选LCA深度大的,这样影响到的点就少。还有选了这个路径,影响到的仅是LCA比他大的,那就很明显是对的了。然后再标记掉整棵以此LCA为根的子树,避免相交。

题外话:判断两条路径相交
对于两条路,较浅的LCA的那条路的两个端点走到与较深的LCA同深度时,若有一个点和LCA相同,那两条路径就相交

#include<bits/stdc++.h>
#define M 100005
using namespace std;
int n,m,fa[18][M],dep[M];
vector<int>edge[M];
struct node{int a,b,lca;}A[M];
void f(int x,int fa1){//造树
    fa[0][x]=fa1;dep[x]=dep[fa1]+1;
    for(int i=0;i<(int)edge[x].size();i++){
        int y=edge[x][i];
        if(y==fa1)continue;
        f(y,x);
    }
}
void Init(){//倍增预处理
    for(int j=1;j<18;j++)
        for(int i=1;i<=n;i++)
            fa[j][i]=fa[j-1][fa[j-1][i]];
}
int LCA(int x,int y){//求LCA
    if(dep[x]>dep[y])swap(x,y);
    int step=dep[y]-dep[x];
    for(int i=0;i<18;i++)
        if(step&(1<<i))y=fa[i][y];
    if(x==y)return x;
    for(int i=17;i>=0;i--)
        if(fa[i][x]!=fa[i][y])x=fa[i][x],y=fa[i][y];
    return fa[0][x];
}
bool cmp(node x,node y){return dep[x.lca]>dep[y.lca];}//按照LCA深度排序
bool Q[M];
void f1(int x,int fa1){//标记掉以这个LCA为根的子树
    Q[x]=1;
    for(int i=0;i<(int)edge[x].size();i++){
        int y=edge[x][i];
        if(y==fa1||Q[y])continue;//避免重复标记
        f1(y,x);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        edge[x].push_back(y);
        edge[y].push_back(x);
    }
    f(1,0);
    Init();
    for(int i=1;i<=m;i++){
        scanf("%d%d",&A[i].a,&A[i].b);
        A[i].lca=LCA(A[i].a,A[i].b);
    }
    sort(A+1,A+1+m,cmp);
    int ans=0;
    for(int i=1;i<=m;i++){//贪心
        if(Q[A[i].a]||Q[A[i].b])continue;
        ans++;
        f1(A[i].lca,fa[0][A[i].lca]);
    }
    printf("%d\n",ans);
    return 0;
}

树的题可以先从链想,然后上升一个高度到树,其实差不多

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值