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);
}
}
}

标签

树状数组

时间: 2024-12-14 22:41:17

2017福建夏令营Day1(数据结构)的相关文章

[FZYZOJ 1283] [NOIP福建夏令营] 修复公路

P1283 -- [NOIP福建夏令营]修复公路 时间限制:1000MS内存限制:131072KB Description A地区在地震过后,连接所有村庄的公路都造成了损坏而无法通车.政府派人修复这些公路. 给出A地区的村庄数N,和公路数M,公路是双向的.并告诉你每条公路的连着哪两个村庄,并告诉你什么时候能修完这条公路.问最早什么时候任意两个村庄能够通车,即最早什么时候任意两条村庄都存在至少一条修复完成的道路(可以由多条公路连成一条道路) Input Format 第1行两个正整数N,M(N<=

FZYZOJ-1578 [NOIP福建夏令营]数列分段

P1578 -- [NOIP福建夏令营]数列分段 时间限制:1000MS      内存限制:131072KB 状态:Accepted      标签:    二分   无   无 Description 对于给定的一个长度为N的正整数数列A[i],现要将其分成M(M≤N)段,并要求每段连续,且每段和的最大值最小. 关于最大值最小: 例如一数列4 2 4 5 1要分成3段 将其如下分段:[4 2][4 5][1] 第一段和为6,第2段和为9,第3段和为1,和最大值为9. 将其如下分段:[4][2

【2016福建省夏令营Day1】数据结构

Problem 1 楼房(build.cpp/c/pas) [题目描述] 地平线(x轴)上有n个矩(lou)形(fang),用三个整数h[i],l[i],r[i]来表示第i个矩形:矩形左下角为(l[i],0),右上角为(r[i],h[i]).地平线高度为0.在轮廓线长度最小的前提下,从左到右输出轮廓线. 下图为样例2. [输入格式] 第一行一个整数n,表示矩形个数. 以下n行,每行3个整数h[i],l[i],r[i]表示第i个矩形. [输出格式] 第一行一个整数m,表示节点个数. 以下m行,每行

2017 济南集训DAY1.AF

LIST T1 水题(water) T2 梦境(dream) T2 动态规划(dp) T1 水题(water) Time Limit:1000ms   Memory Limit:128MB 题目描述 LYK出了道水题. 这个水题是这样的:有两副牌,每副牌都有n张. 对于第一副牌的每张牌长和宽分别是xi和yi.对于第二副牌的每张牌长和宽分别是aj和bj.第一副牌的第i张牌能覆盖第二副牌的第j张牌当且仅当xi>=aj并且yi>=bj.(注意牌不能翻转)当然一张牌只能去覆盖最多一张牌,而不能覆盖好多

2017省夏令营Day7 【快速幂,筛法,矩阵快速幂,线段树】

题解:首先,我们可以得到一个规律:经过2次变换后,a和b的值都分别乘2了,所以只要用快速幂就能过啦,但是,要特判n为0的情况. 代码如下: 1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define Mod 1000000007 5 using namespace std; 6 long long a,b,n,ans1,ans2; 7 long long power(long long x)

2017省夏令营Day6 【dp】

题解:区间dp,f[i][j]表示区间[i,j]的狼全部消灭的最小代价,设k为i.j间任意一点(i<=k<=j),且第k只狼被最后消灭,显然,区间总代价即可被我们划分成[i,k-1]和[k+1,j]两部分,我们可以假设他们已知,于是求得两区间代价和再加上消灭第k只狼的代价就能求得区间[i,j]的总代价. 状态转移方程:f[i][j]=f[i][k-1]+f[k+1][i]+a[k]+b[i-1]+b[j+1]. PS:注意初始化时i要从0开始枚举,且若j<i时f值为0. 代码如下: 1

清北学堂学习总结 day1 数据结构 练习

1.二叉搜索树 STL set直接做就可以了 2.树状数组+差分数列: codevs 1081 线段树练习 2 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 大师 Master 题目描述 Description 给你N个数,有两种操作 1:给区间[a,b]的所有数都增加X 2:询问第i个数是什么? 输入描述 Input Description 第一行一个正整数n,接下来n行n个整数,再接下来一个正整数Q,表示操作的个数. 接下来Q行每行若干个整数.如果第一个数是1,后接3个正

2017省夏令营Day8 【bfs,并查集】

题解:出题人丧心病狂~ 对于这道题,我们对每一个内应节点bfs,并用并查集维护,如果s和t联通,输出答案并break. PS几个小细节:①对于每个内应dis=0,为了保证不会对答案产生影响,我们在每2个节点中插入一个新的节点即可: ②因为加入新节点,数组要开大些,否则会炸. 代码如下: 1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define Max 2020304 5 using nam

ZJOI 2017 二试 day1 4.26

day0,11:30熄灯,又因为在房间里太浪,空调开了28度,过了好久才成功降温,导致睡得不太好QaQ. 于是早上昏昏欲睡,也没怎么听懂(orz孙耀峰). 中午大家一致提议下午不去听课,回到房间浪了好久,直到1:30LJ在群里怒表我们一顿,才慌忙跑到余姚中学,准时到达. 然后一个下午都没怎么听课,对就是这样. 晚上试机(电脑好差!)+扑克牌+打游戏(停不下来),虽然浪,但毕竟一年就这么几天,也不要紧. 明天要早点睡,后天加油.