BestCoder Round #43 (hdu5264 - 5266)(数学,LCA)good

本文介绍了三道算法题目,涉及字符串处理与树结构的最近公共祖先查询,提供了详细的解题思路与代码实现。

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

pog loves szh I

Accepts: 497
Submissions: 822
Time Limit: 2000/1000 MS (Java/Others)
Memory Limit: 65536/65536 K (Java/Others)
问题描述
pog拥有很多字符串,它喜欢将两个长度相等字符串交错拼在一起,如abcd与efgh,那么交错拼在一起就成了aebfcgdh啦!
szh觉得这并不好玩,因此它将第二个字符串翻转了一遍,如efgh变成了hgfe,然后再将这两个字符串交错拼在一起,因此abcd与efgh就成了ahbgcfde啦!
现在问题来了,pog手里有一个由szh亲手拼好的字符串,它想还原出原来的两个字符串,出于szh对pog的爱,szh帮助pog还原出了原来的两个字符串。
输入描述
第一行读入一个整数
  
  
   
   T(1T100)
  
  ,表示有
  
  
   
   T
  
  组数据。
接下来
  
  
   
   T
  
  行每行读入一个长度为偶数且仅包含英文小写字母的字符串
  
  
   
   (|S|100)
  
  
输出描述
对于每组数组,输出两行,表示原来的两个字符串。
输入样例
1
aabbca
输出样例
abc
aba
Hint
本题在hack时行末需要换行
水题不解释

#include<bits/stdc++.h>
using namespace std;
string ans1,ans2,a;
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        cin>>a;
        ans1="",ans2="";
        for(int i=0;i<a.size();i++)
        {
            if((i&1)==0)ans1+=a[i];
            else ans2+=a[i];
        }
        reverse(ans2.begin(),ans2.end());
        cout<<ans1<<endl<<ans2<<endl;
    }
    return 0;
}

pog loves szh II

Accepts: 97
Submissions: 834
Time Limit: 4000/2000 MS (Java/Others)
Memory Limit: 65536/65536 K (Java/Others)
Problem Description

Pog and Szh are playing games. There is a sequence with  n  numbers, Pog will choose a number A from the sequence. Szh will choose an another number named B from the rest in the sequence. Then the score will be  (A+B)  mod  p . They hope to get the largest score. And what is the largest score?

Input

Several groups of data (no more than 5 groups, n1000 ).

For each case:

The following line contains two integers, n(2n100000) p(1p2311)

The following line contains n integers ai(0ai2311)

Output

For each case,output an integer means the largest score.

Sample Input
4 4
1 2 3 0
4 4
0 0 2 2
Sample Output
3
2

思路:首先每个数对P取模,然后排序,那么任意两个数相加,要么小于p,要么小于2*p,因为我们找尽量大的,所以对于x这个数,我们找p-x-1或者2*p-x-1是不是存在,二分一下就行了,要注意的是两个数的位置不能重叠

下面是官方发布的题解,找的时候不用二分,直接双指针扫一遍

由于序列中的数可能超过
  
  
   
   P
  
  ,所以将所有的数读入后进行取模操作。之后将取模后的所有数从小到大排序。题目要求我们求不同位置的两个数的和在取模意义下的最大值,而现在所有数都是小于
  
  
   
   P
  
  且排好序的。因此设我任意选了两个数是
  
  
   
   X
  
  
  
  
   
   Y
  
  ,显然
  
  
   
   0X+Y2P2
  
  。若
  
  
   
   X+Y<P
  
  ,则这次选数的答案就是
  
  
   
   X+Y
  
  ,若
  
  
   
   X+YP
  
  ,则答案是
  
  
   
   X+YP
  
  。
那么我们可以这样做:将其中最大的两个数字相加取模,设为一个可能的答案记录在ANS中。这个答案是第二种情况的最大值。再对排序好的序列进行枚举,对每个枚举到的数,找出最大的数,使这个数与枚举到的数相加后的和最大且小于P,将之记为可能的答案并于之前找到的最大值ANS进行比较。这个答案是第一种情况中的可能的最大值。而普通的枚举是要超时的,但是我们发现如果从小到大枚举第一个数,那么另一个匹配的数显然是从大到小的,因此可以用一个NOW记录当前另一个匹配的数的位置,每次枚举时NOW递减至符合条件。可以做到
  
  
   
   O(n)
  
  的时间复杂度。
综上所述,时间复杂度为快速排序的
  
  
   
   O(nlogn)
  
  ,空间复杂度为
  
  
   
   O(n)
  
  。注意一些特殊情况如同一个位置不能多次选。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=100010;
int N;
LL a[maxn],P;
map<LL,int> mp;
void solve()
{
    LL ans=0;
    for(int i=1;i<=N;i++)
    {
        LL x=a[i];
        int pos=lower_bound(a+1,a+1+N,P-1-x)-a;
        if(pos>N)pos=N;
        if(a[pos]>P-x-1)pos--;
        if(pos>0&&(pos!=i||mp[x]>1))ans=max(ans,(x+a[pos])%P);
        pos=lower_bound(a+1,a+1+N,P*2-x-1)-a;
        if(pos>N)pos=N;
        if(a[pos]>2LL*P-x-1)pos--;
        if(pos>0&&(pos!=i||mp[x]>1))ans=max(ans,(x+a[pos])%P);
    }
    printf("%I64d\n",ans);
}
int main()
{
    while(scanf("%d%I64d",&N,&P)!=EOF)
    {
        mp.clear();
        for(int i=1;i<=N;i++)
        {
            scanf("%I64d",&a[i]);
            a[i]%=P;
            mp[a[i]]++;
        }
        sort(a+1,a+1+N);
        N=unique(a+1,a+1+N)-a-1;
        solve();
    }
    return 0;
}

pog loves szh III

Accepts: 63
Submissions: 483
Time Limit: 12000/6000 MS (Java/Others)
Memory Limit: 131072/131072 K (Java/Others)
问题描述
pog在与szh玩游戏,首先pog在纸上画了一棵有根树,这里我们定义1为这棵树的根,然后szh在这棵树中选了若干个点,想让pog帮忙找找这些点的最近公共祖先在哪里,一个点
为S的最近公共祖先当且仅当以该点为根的子树包含S中的所有点,且该点深度最大。然而,这个问题是十分困难的,出于szh对pog的爱,他决定只找编号连续的点,即
  
  
   
   li
  
  ~
  
  
   
   ri
  
  
输入描述
若干组数据(不超过
  
  
   
   3
  
  
  
  
   
   n10000
  
  
  
  
   
   Q10000
  
  )。
每组数据第一行一个整数
  
  
   
   n(1n300000)
  
  ,表示树的节点个数。
接下来
  
  
   
   n1
  
  行,每行两个数
  
  
   
   AiBi
  
  ,表示存在一条边连接这两个节点。
接下来一行一个数
  
  
   
   Q(1Q300000)
  
  ,表示有
  
  
   
   Q
  
  组询问。
接下来Q行每行两个数
  
  
   
   li,ri(1lirin)
  
  ,表示询问编号为
  
  
   
   li
  
  ~
  
  
   
   ri
  
  的点的最近公共祖先。
输出描述
对于每组的每个询问,输出一行,表示编号为li~ri的点的最近公共祖先的编号。
输入样例
5
1 2
1 3
3 4
4 5
5
1 2
2 3
3 4
3 5
1 5
输出样例
1
1
3
3
1
Hint
珍爱生命,远离爆栈。

思路:对于区间(l,r)的最近公共祖先,是其中dfs序最小的和最大的两个数的最近公共祖先,因为最小的说明是最早的一个分支,反之是最大的,所以他两个的最近公共祖先可以代表整个区间的。那么这个问题就变得很简单了,首先dfs把该标记的都标记了,然后初始化RMQ(用来查询区间dfs序最大最小),询问的时候只需要查出区间最大最小的是谁,然后求这两个的LCA

下面是官方给的题解:

做这题的方法有很多。下面给出2种解法。
1:维护一个跳表,表示编号为
  
  
   
   i
  
  ~
  
  
   
   i+2j1
  
  的LCA,注意在这里求LCA必须用
  
  
   
   O(1)
  
  的做法才能通过所有数据。可以转换为RMQ,每次查询时只需查询两个数的LCA即可。
2:考虑dfs序,通过在简单的证明可知L~R的LCA为
  
  
   
   L
  
  ~
  
  
   
   R
  
  中dfs序较小的那个位置与dfs序较大的那个位置的LCA。因此只要通过st表处理L~R最大dfs序与最小dfs序的编号即可。
另外本题可能会出现爆栈的问题,可以通过黑科技避免,或者用bfs来代替dfs,用bfs维护dfs序的方法比较简单不再阐述。
复杂度为
  
  
   
   O(nlgn)
  
  
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")
using namespace std;

const int maxn=300010;

int N,Q,dfs_clock;
int a[maxn];
int d1[maxn][20],d2[maxn][20];
int dep[maxn],fa[maxn],anc[maxn][20];
int num[maxn],cnt[maxn];
vector<int> G[maxn];
void dfs1(int u,int f,int depth)
{
    cnt[u]=++dfs_clock;
    dep[u]=depth;
    fa[u]=f;
    num[u]=1;
    int len=G[u].size();
    for(int i=0;i<len;i++)
    {
        int v=G[u][i];
        if(v==f)continue;
        dfs1(v,u,depth+1);
        num[u]+=num[v];
        fa[v]=u;
    }
}
void preprocess()
{
    //处理最近公共祖先的RMQ
    for(int i=1;i<=N;i++)
    {
        anc[i][0]=fa[i];
        for(int j=1;(1<<j)<=N;j++)
            anc[i][j]=-1;
    }
    for(int j=1;(1<<j)<=N;j++)
    {
        for(int i=1;i<=N;i++)
        {
            if(anc[i][j-1]==-1)continue;
            int a=anc[i][j-1];
            anc[i][j]=anc[a][j-1];
        }
    }
    //处理dfs序的RMQ
    for(int i=0;i<=N;i++)
        d1[i][0]=d2[i][0]=i;
    for(int j=1;(1<<j)<=N;j++)
        for(int i=1;i+(1<<j)<=N+1;i++)
        {
            int x=d1[i][j-1],y=d1[i+(1<<(j-1))][j-1];
            if(cnt[x]<=cnt[y])d1[i][j]=x;
            else d1[i][j]=y;

            x=d2[i][j-1],y=d2[i+(1<<(j-1))][j-1];
            if(cnt[x]>=cnt[y])d2[i][j]=x;
            else d2[i][j]=y;
        }

}
//询问最近公共祖先
int LCA(int p,int q)
{
    if(dep[p]<dep[q])swap(p,q);
    int k=0;
    while((1<<(k+1))<=dep[p])k++;
    for(int i=k;i>=0;i--)
        if(dep[p]-(1<<i)>=dep[q])
            p=anc[p][i];
    if(p==q)return p;
    for(int i=k;i>=0;i--)
        if(anc[p][i]!=-1&&anc[p][i]!=anc[q][i])
            p=anc[p][i],q=anc[q][i];
    return fa[p];
}
//询问dfs序最小的跟最大的
void query(int l,int r,int &x,int &y)
{
    int k=0;
    while((1<<(k+1))<=(r-l+1))k++;
    int a=d1[l][k],b=d1[r-(1<<k)+1][k];
    if(cnt[a]<=cnt[b])x=a;
    else x=b;

    a=d2[l][k],b=d2[r-(1<<k)+1][k];
    if(cnt[a]>=cnt[b])y=a;
    else y=b;
}
int main()
{
    int u,v;
    while(scanf("%d",&N)!=EOF)
    {
        for(int i=0;i<=N;i++)G[i].clear();
        for(int i=1;i<N;i++)
        {
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        dfs_clock=0;
        dfs1(1,0,0);
        preprocess();
        scanf("%d",&Q);
        while(Q--)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            query(l,r,u,v);
            printf("%d\n",LCA(u,v));
        }
    }
    return 0;
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值