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); } } }
标签
树状数组