工作团队
【问题描述】 一家公司有??名员工,刚开始每个人单独构成一个工作团队。 有时一项工作仅凭一个人或一个团队难以完成,所以公司会让某两个 人所在的团队合并。 但有的工作属于闷声大发财类型的,不适合多人做,所以公司有时也 会让一个人从他当前所在的团队中分离出来,构成单独的团队。 公司也要对当前团队的情况进行了解,所以他们也会询问一些问题, 比如某两个人是否属于同一工作团队,某个人所在的团队有多少个人,或 者当前一共有多少个工作团队。 作为该公司的软件服务商,你的任务便是实现一个实时的操作和查询 系统。
【输入格式】 每个测试点第一行有两个正整数??, ??,表示员工数和公司的指令数。 接下来??行,每行的格式为下列所述之一: 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);
}
}
}
标签
树状数组