2017福建夏令营Day1(数据结构)

工作团队

【问题描述】 一家公司有𝑛名员工,刚开始每个人单独构成一个工作团队。 有时一项工作仅凭一个人或一个团队难以完成,所以公司会让某两个 人所在的团队合并。 但有的工作属于闷声大发财类型的,不适合多人做,所以公司有时也 会让一个人从他当前所在的团队中分离出来,构成单独的团队。 公司也要对当前团队的情况进行了解,所以他们也会询问一些问题, 比如某两个人是否属于同一工作团队,某个人所在的团队有多少个人,或 者当前一共有多少个工作团队。 作为该公司的软件服务商,你的任务便是实现一个实时的操作和查询 系统。

【输入格式】 每个测试点第一行有两个正整数𝑛, 𝑚,表示员工数和公司的指令数。 接下来𝑚行,每行的格式为下列所述之一: 1 𝑢 𝑣,表示将第𝑢个人与第𝑣个人所在的团队合并,如果两个人所在团 队相同,则不执行任何操作。 2 𝑤,表示使第𝑤个人从当前的团队中分离出来,如果第𝑤个人不在任 何多人团队中,则不执行任何操作。 3 𝑥 𝑦,表示询问𝑥, 𝑦两个人是否在同一个工作团队,是的话回答𝑌 𝑒𝑠, 否则回答𝑁𝑜。 4 𝑧,表示询问第𝑧个人所在的工作团队一共有多少个人。 5,询问当前一共有多少个工作团队。

【输出格式】 对每个询问输出一行相应的值表示答案。

【样例输入】 3 11 1 1 2 3 2 3 4 2 1 2 3 3 2 3 4 2 5 2 2 3 2 3 4 2 5

【样例输出】 𝑁𝑜 2 𝑌 𝑒𝑠 3 1 𝑁𝑜 1 2

【数据规模】 Easy:对于30%的数据,不存在2,4,5操作。 Normal:对于70%的数据,不存在2操作。 Hard:对于100%的数据,均有1 ≤ 𝑛 ≤ 50000, 1 ≤ 𝑚 ≤ 100000

 

题解

对于1.3.4.5操作
1.fa[getfa(x)]=getfa(y),size[getfa(y)]+=size[getfa(x)],cnt--
3.if(getfa(x)==getfa(y))?
4.cout<<size[getfa(x)]
5.cout<<cnt
很简单是吧

对于2操作很复杂
因为如果你取出的是这个集合所有点的父亲,路径压缩后是不好维护删除的
怎么办?
不去理它。
建立一个新的点表示这个旧的点。之前的操作在旧点完成,保证树的结构。之后的操作在所建立的新的点上实现

 

代码

#include<bits/stdc++.h>
using namespace std;
const int N=100050,M=200100;
int n,m,num[N]={},now=0,totg=0,root[N+M]={},size[N+M]={};
int get_root(int r)
{
    if(r!=root[r])
        root[r]=get_root(root[r]);
    return root[r];
}
int main()
{
    freopen("workteam.in","r",stdin);
    freopen("workteam.out","w",stdout);
    scanf("%d%d",&n,&m);
    now=totg=n;
    for(int i=1;i<=n;++i)
        num[i]=root[i]=i,size[i]=1;
    int t,u,v;
    for(int i=1;i<=m;++i)
    {
        scanf("%d",&t);
        if(t==1)
        {
            scanf("%d%d",&u,&v);
            int ru=get_root(num[u]),rv=get_root(num[v]);
            if(ru!=rv)
            {
                --totg;
                root[ru]=rv;
                size[rv]+=size[ru];
                size[ru]=0;
            }
        }
        if(t==2)
        {
            scanf("%d",&u);
            int ru=get_root(num[u]);
            if(size[ru]>1)
            {
                ++totg;
                num[u]=++now;
                root[now]=now;
                size[now]=1;
                --size[ru];
            }
        }
        if(t==3)
        {
            scanf("%d%d",&u,&v);
            puts(get_root(num[u])==get_root(num[v]) ? "Yes" : "No");
        }
        if(t==4)
        {
            scanf("%d",&u);
            printf("%d\n",size[get_root(num[u])]);
        }
        if(t==5)
            printf("%d\n",totg);
    }
}

 

标签

并查集

 

单词表

【问题描述】 ℎ𝑧𝑤𝑒𝑟获得了一个𝑛个单词的单词表,其中每个字符都是小写字母,现 在,他想和他的妹子研究一下这个单词表。 设编号为𝑢的单词与编号为𝑣的单词(𝑢 < 𝑣)构成单词对(𝑢, 𝑣),记两 个单词最长公共前缀为𝑆1(𝑢, 𝑣),最长公共后缀为𝑆2(𝑢, 𝑣)。 ℎ𝑧𝑤𝑒𝑟从𝑆1入手,他想知道,所有单词对生成的𝑆1串中,长度最大的 串,如果有多个,那么他在其中选择字典序最小的,并且你要告诉他𝑆1串 为该串的单词对数,而他的妹子则关心𝑆2,要求类似。 但是ℎ𝑧𝑤𝑒𝑟要和他的妹子去度假,所以这个问题就交给你了。 保证至少存在一个长度大于0的𝑆1串和𝑆2串。

【输入格式】 第一行一个正整数𝑛,表示单词个数。 接下来𝑛行,每行一个字符串,表示一个单词。 【输出格式】 第一行输出最长的基础上字典序最小的𝑆1串,以及𝑆1串为该串的单词 对个数,用一个空格隔开。 第二行输出最长的基础上字典序最小的𝑆2串,以及𝑆2串为该串的单词 对个数,用一个空格隔开。

【样例输入】 5 bbbaa aacbb bbdaa aaaaa bbcaa

【样例输出】 aa 1 aa 6

【数据规模】 Easy:对于20%的数据,𝑛 ≤ 10。 Normal:对于60%的数据,𝑛 ≤ 500,字符串总长不超过50000。 Hard:对于100%的数据,2 ≤ 𝑛 ≤ 50000,字符串总长不超过500000, 保证单词表中所有字符都是小写字母,答案中𝑆1串和𝑆2串非空。

 

 

题解

Trie树
先用Trie树维护输入字符串。
因为前后缀只要把字符串反过来做即可
以前缀为例
size[u]表示u到trie树的根这条链是多少字符串的前缀
每经过一条节点就把size[u]+=1
当size[u]>=2时就意味着这个点之前的路径是两条路径,就拿去比对答案
得出最长前缀后只要从根节点开始去dfs(保证路径size[u]>=2为合法路径)找到字典序最小的,然后输出最后一个点的size[u]*(size[u]-1)/2即可

 

代码

#include<bits/stdc++.h>
using namespace std;
const int L=500500,C=26;
int n;
int tot1=0,tr1[L][C]={},size1[L]={},maxl1=0;
int tot2=0,tr2[L][C]={},size2[L]={},maxl2=0;
long long sum1=0,sum2=0;
char ch[L]={},ans1[L]={},ans2[L]={},tmp[L]={};
void init()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("\n%s",ch+1);
        int l=strlen(ch+1);
        int p1=0,p2=0;
        for(int j=1;j<=l;++j)
        {
            if(tr1[p1][ch[j]-'a']==0)
                tr1[p1][ch[j]-'a']=++tot1;
            p1=tr1[p1][ch[j]-'a'];
            ++size1[p1];
            if(size1[p1]>=2)
                maxl1=max(j,maxl1);
        }
        for(int j=l;j>=1;--j)
        {
            if(tr2[p2][ch[j]-'a']==0)
                tr2[p2][ch[j]-'a']=++tot2;
            p2=tr2[p2][ch[j]-'a'];
            ++size2[p2];
            if(size2[p2]>=2)
                maxl2=max(l+1-j,maxl2);
        } 
    }
}
void dfs1(int p,int d)
{
    if(d==maxl1)
    {
        bool flag=false;
        for(int i=0;i<d;++i)
        {
            if(tmp[i]<ans1[i])
            {
                flag=true;
                break;
            }
            if(tmp[i]>ans1[i])
                break;
        }
        if(flag)
        {
            copy(tmp,tmp+d,ans1);
            sum1=size1[p]*1ll*(size1[p]-1)/2;
        }
        return;
    }
    for(int i=0;i<C;++i)
        if(tr1[p][i] && size1[tr1[p][i]]>=2)
        {
            tmp[d]='a'+i;
            dfs1(tr1[p][i],d+1);
            tmp[d]=0;
        }
}
void dfs2(int p,int d)
{
    if(d==maxl2)
    {
        bool flag=false;
        for(int i=d-1;i>=0;--i)
        {
            if(tmp[i]<ans2[i])
            {
                flag=true;
                break;
            }
            if(tmp[i]>ans2[i])
                break;
        }
        if(flag)
        {
            copy(tmp,tmp+d,ans2);
            sum2=size2[p]*1ll*(size2[p]-1)/2;
        }
        return;
    }
    for(int i=0;i<C;++i)
        if(tr2[p][i] && size2[tr2[p][i]]>=2)
        {
            tmp[d]='a'+i;
            dfs2(tr2[p][i],d+1);
            tmp[d]=0;
        }
}
void work()
{
    fill(ans1,ans1+maxl1,'z'+1);
    fill(ans2,ans2+maxl2,'z'+1);
    dfs1(0,0);
    printf("%s %I64d\n",ans1,sum1);
    dfs2(0,0);
    reverse(ans2,ans2+maxl2);
    printf("%s %I64d\n",ans2,sum2);
}
int main()
{
    freopen("wordlist.in","r",stdin);
    freopen("wordlist.out","w",stdout);
    init();
    work();
}

 

标签

字典树

 

数列编辑器

【问题描述】 现在你需要实现一个数列编辑器,一开始,数列为空,光标在开头位 置,编辑器要支持对这个数列进行如下六种操作: 𝐼 𝑥:在光标的后面插入一个整数𝑥,并将光标移到这个新加入的𝑥后。 𝐷:删除光标前的最后一个数字(保证存在),光标位置不变。 𝐿:光标左移一位,如果已经在开头则不做任何事。 𝑅:光标右移一位,如果已经在结尾则不做任何事。 𝑄 𝑙 𝑟:求当前序列中第𝑙到第𝑟个数(包含边界,保证存在)的和。 𝐶 𝑝 𝑥:将当前序列第𝑝个数(保证存在)修改成整数𝑥,光标不移动。

【输入格式】 第一行,一个整数𝑛,表示操作的总次数。 后𝑛行,每行是上列六种操作中的一种。

【输出格式】 对每个询问输出一行一个整数,表示答案。

【样例输入】 9 𝐼 2 𝐼 -1 𝐼 1 𝑄 1 2 𝐿 𝐷 𝑄 1 2 𝐼 -3 𝑄 1 2 【样例输出】 1 3 -1 【数据规模】 Easy:第1-2个测试点,1 ≤ 𝑛 ≤ 5000。 Normal:第3-4个测试点,不存在𝐿, 𝑅, 𝐶操作。 Hard:第5-7个测试点,不存在𝐿, 𝑅操作。 Extra:对于100%的数据,存在全部操作,且1 ≤ 𝑛 ≤ 5 × 105,记当前 数列长度为𝐿,则操作中-109 ≤ 𝑥 ≤ 109,1 ≤ 𝑙 ≤ 𝑟 ≤ 𝐿,且1 ≤ 𝑝 ≤ 𝐿。

题解

如果没有3.4两种操作就可以用线段树
于是以光标为界限
维护两个树状数组(光标前与后)
栈底为序列两端,栈顶为光标处
删除加入操作在序列左端的树状数组维护
插入和删除元素即为在前半部分的栈中插入或弹出元素,而光标移动则相当于把一个部分的栈顶弹出塞入另一个栈
时间复杂度O(nlogn)

#include<bits/stdc++.h>
using namespace std;
const int N=500500;
template <typename T> class stack_BIT
{
    T a[N],t[N];
    int capacity,pos;
    void add(int p,T c)
    {
        for(; p<=capacity; p+=p&(-p))
            t[p]+=c;
    }
    T presum(int p) const
    {
        T s=0;
        for(; p; p-=p&(-p))
            s+=t[p];
        return s;
    }
    public:
    void init(int n)
    {
        capacity=n;
        pos=0;
        fill(a,a+N,0);
        fill(t,t+N,0);
    }
    void push(T x)
    {
        ++pos;
        add(pos,x);
        a[pos]=x;
    }
    void pop()
    {
        add(pos,-a[pos]);
        a[pos--]=0;
    }
    T top() const
    {
        return a[pos];
    }
    void change(int p,T c)
    {
        add(p,c-a[p]);
        a[p]=c;
    }
    T sum(int l,int r) const
    {
        return presum(r)-presum(l-1);
    }
    int size() const
    {
        return pos;
    }
    bool empty() const
    {
        return pos==0;
    }
};
stack_BIT<long long> pre,suf;
int n;
int main()
{
    freopen("editor.in","r",stdin);
    freopen("editor.out","w",stdout);
    scanf("%d",&n);
    pre.init(n);
    suf.init(n);
    char ch;
    int a,b;
    for(int i=1;i<=n;++i)
    {
        scanf("\n%c",&ch);
        if(ch=='I')
        {
            scanf("%d",&a);
            pre.push(a);
        }
        if(ch=='D')
            pre.pop();
        if(ch=='L' && !pre.empty())
        {
            suf.push(pre.top());
            pre.pop();
        }
        if(ch=='R' && !suf.empty())
        {
            pre.push(suf.top());
            suf.pop();
        }
        if(ch=='Q')
        {
            scanf("%d%d",&a,&b);
            int s1=pre.size(),s2=suf.size();
            if(b<=s1)
                printf("%I64d\n",pre.sum(a,b));
            else
                if(a>s1)
                    printf("%I64d\n",suf.sum(s2-(b-s1-1),s2-(a-s1-1)));
                else
                    printf("%I64d\n",pre.sum(a,s1)+suf.sum(s2-(b-s1-1),s2));
        }
        if(ch=='C')
        {
            scanf("%d%d",&a,&b);
            int s1=pre.size(),s2=suf.size();
            if(a<=s1)
                pre.change(a,b);
            else
                suf.change(s2-(a-s1-1),b);
        }
    }
}

 

标签

树状数组

posted @ 2017-08-23 13:58  小白的小白  阅读(303)  评论(0编辑  收藏  举报