练习赛42
Begin:
2019-03-30 18:35
PDD:275rlb
A
Blood Cousins CodeForces - 208E
题目大意:
在一个森林中,如果两个节点a和b向上的第p个祖先相同,就称他们为p代表亲。(跟日常生活中有所不同,p不一定是他们的最近公共祖先)。给出一些询问,问v的p代表亲的数量。
思路:
首先注意题目,这道题是一个森林,而并非一棵树,所以要保存每棵树的根结点。
这道题需要把每个询问存在v的p代祖先上,而并非在v上。其他基本上就是dsu on tree 的模板。
CODE:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int sz[N],dis[N],f[N][20];
int li[N],ri[N],tot,ID[N];
int cnt[N],ans[N];
bool no[N];
int n,m;
vector<int>e[N];
struct node{int id,d;};
vector<node>q[N];
void dfs(int x){
sz[x]=1,li[x]=++tot,ID[tot]=x;
for(int i=0;i<e[x].size();i++){
int y=e[x][i];
dis[y]=dis[x]+1;
f[y][0]=x;
dfs(y);
sz[x]+=sz[y];
}
ri[x]=tot;
}
void add(int x){for(int i=li[x];i<=ri[x];i++)cnt[dis[ID[i]]]++;}
void del(int x){for(int i=li[x];i<=ri[x];i++)cnt[dis[ID[i]]]--;}
void solve(int x){
int son=0;
for(int i=0;i<e[x].size();i++)
if(!son||sz[son]<sz[e[x][i]])son=e[x][i];//找重儿子
for(int i=0;i<e[x].size();i++)
if(e[x][i]!=son)solve(e[x][i]),del(e[x][i]);
if(son)solve(son);
for(int i=0;i<e[x].size();i++)
if(e[x][i]!=son)add(e[x][i]);
cnt[dis[x]]++;
for(int i=0;i<q[x].size();i++)
ans[q[x][i].id]=cnt[dis[x]+q[x][i].d]-1;//不包含自己要-1
}
int UP(int x,int p){
for(int i=0;i<=17;i++)
if(p&(1<<i))x=f[x][i];
return x;
}
int main(){
scanf("%d",&n);
for(int i=1,p;i<=n;i++){
scanf("%d",&p);
if(p==0)no[i]=1;//是某棵树的根结点
else e[p].push_back(i);
}
for(int i=1;i<=n;i++)if(no[i])dfs(i);//把每棵树都扫一遍
for(int i=1;i<=17;i++)
for(int j=1;j<=n;j++)f[j][i]=f[f[j][i-1]][i-1];
scanf("%d",&m);
for(int i=1,a,b;i<=m;i++){
scanf("%d%d",&a,&b);
int fa=UP(a,b);//a往上b个节点:a的b代“祖先”
q[fa].push_back((node){i,b});//把询问存于祖先上
}
for(int i=1;i<=n;i++)
if(no[i]){
for(int j=0;j<=n;j++)cnt[j]=0;//注意每棵树都是独立的,最好初始化一下
solve(i);
}
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
}
B
Tree Intersection CSU - 1811
题目大意:
给定一棵树,它有n-1条边。如果把第i条边删掉之后,会形成两个联通块。你需要回答有多少种颜色同时出现在这两个联通块中。
思路:
先吐槽一句CSU这个破玩意儿,竟然把WA判成输出超限,de了半天quq
还是树上启发式合并的模板,主要问题在于如何修改。
1.一种暴力的思路就是,把当前遍历到的节点的颜色到存于一个map中,然后每次询问是把这个map里的元素跟总的个数比较,再计算。这样每次询问都要O(N),亲测TLE。
2.那么我们不仅要累积颜色还要直接在修改过程中更改res。
我们注意到,每次新加入一个节点v时,设其颜色为co:
void add(int x){
step1 如果cnt[co]==0时,即当前集合中还没有这个颜色,那么res++;
step2 我们再把cnt[co]++;
step3 如果此时发现cnt[co]==all[co],即集合外没这种颜色了,那么res--.
}
每次从集合中删除一个节点v时,设其颜色为co:
void del(int x){
step1 如果cnt[co]==all[co],我们可以理解为,集合外会第一次加入这个要删除的元素所具有的颜色, 那么res++;
step2 cnt[co]--;
step3 如果此时cnt[co]==0 ,即集合中没有这个颜色了,那么res--.
}
CODE:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int all[N],ans[N],res,now[N];
map<int,int>query[N];
vector<int>e[N];
int a[N],li[N],ri[N],ID[N],tot,sz[N];
inline int read(){
int x=0; char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x;
}
void dfs(int x,int fa){
sz[x]=1,li[x]=++tot,ID[tot]=x;
for(int i=0;i<e[x].size();i++){
int y=e[x][i];
if(y==fa)continue;
dfs(y,x);
sz[x]+=sz[y];
}
ri[x]=tot;
}
void add(int x){
for(int i=li[x];i<=ri[x];i++){
int p=ID[i];
if(now[a[p]]==0)res++;
now[a[p]]++;
if(now[a[p]]==all[a[p]])res--;
}
}
void del(int x){
for(int i=li[x];i<=ri[x];i++){
int p=ID[i];
if(now[a[p]]==all[a[p]])res++;
now[a[p]]--;
if(now[a[p]]==0)res--;
}
}
void solve(int x,int fa){
int son=0;
for(int i=0;i<e[x].size();i++){
if(e[x][i]==fa)continue;
if(!son||sz[e[x][i]]>sz[son])son=e[x][i];
}
for(int i=0;i<e[x].size();i++){
int y=e[x][i];
if(y!=son&&y!=fa)solve(y,x),del(y);
}
if(son)solve(son,x);
for(int i=0;i<e[x].size();i++){
int y=e[x][i];
if(y!=son&&y!=fa)add(y);
}
if(now[a[x]]==0)res++;
now[a[x]]++;
if(now[a[x]]==all[a[x]])res--;
if(fa)ans[query[x][fa]]=res;
}
int main(){
int n;
while(~scanf("%d",&n)){
res=0,tot=0;
for(int i=1;i<=n;i++)now[i]=all[i]=0,query[i].clear(),e[i].clear();
for(int i=1;i<=n;i++)++all[a[i]=read()];
for(int i=1,a,b;i<n;i++){
a=read(),b=read();
e[a].push_back(b);
e[b].push_back(a);
query[a][b]=query[b][a]=i;//注意,因为无法确定谁深度小
}
dfs(1,0);
solve(1,0);
for(int i=1;i<n;i++)printf("%d\n",ans[i]);
}
}
C
Duff in the Army CodeForces - 587C
题目大意:
有n个城市,由n-1条边连接。两个城市之间的道路是唯一的。
有m个人,住在这n个城市中,现在给出m个人,生活的城市编号。
你需要回答,从一个城市u到另一个城市v的路径中,编号前a小的人的编号是哪些?
思路:
用倍增的方法,把节点v到其所有祖先的路径上居民编号前10大的保存下来,(a<=10)这点很重要,所以每次只保留至多10个元素。路径的合并可以用归并排序解决,因为每段都是从小到大排的,是顺序的。另外注意一些细节.
ps:还有线段树写法。此题最好不用vector,不然跑的很慢,下面代码用了vector,跑的比较慢,但比较好打。
CODE:
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=1e5+5;
int f[N][20],dep[N];
typedef vector<int> ve;
vector<int> now;
vector<int> a[N][20],city[N],e[N];
vector<int> UP(ve x,ve y){//合并两段路径:归并排序
int i=0,j=0;
vector<int> res;
res.clear();//尤其注意要初始化
while(i<x.size()&&j<y.size()&&res.size()<10){
if(x[i]<y[j])res.pb(x[i++]);
else res.pb(y[j++]);
}
while(i<x.size()&&res.size()<10)res.pb(x[i++]);
while(j<y.size()&&res.size()<10)res.pb(y[j++]);
return res;
}
void dfs(int x,int fa,int d){
dep[x]=d,f[x][0]=fa,f[x][1]=f[fa][0];
//深度 f的意义跟其他题目没区别
a[x][0]=city[x],a[x][1]=UP(city[x],city[fa]);
/*a[x][0]=city[x]这里直接复制了city[x],至于前面为什么city不sort(city.begin(),city.end())也能过,我也不太清楚,按理来说如果一个城市有多个居民,应当先要sort一遍,以保证后面归并排序的正确性,可能数据原因吧
*/
//a[x][0]存的单单是自己,a[x][i]存的是由自己到向上(2^i)-1个节点的路径
//这与平常的倍增数组不太一样,因为此题还要考虑自己也算在集合中
for(int i=0;i<e[x].size();i++){
int y=e[x][i];
if(y!=fa)dfs(y,x,d+1);
}
}
void LCA(int u,int v){
//联通u到v路径,其实函数名叫LCA不太恰当,但统计路径的方法跟求LCA是一样的就这么叫着吧
now.clear();//注意初始化
if(dep[u]>dep[v])swap(u,v);
int step=dep[v]-dep[u];
for(int i=17;i>=0;i--)
if(step&(1<<i))now=UP(now,a[v][i]),v=f[v][i];
//与求LCA的步骤一样,先把u,v搞到同一深度去,但别忘了一边合并路径
if(u==v){
now=UP(now,a[u][0]);
return;
}/*这时候u,v都在一起了,但注意,根据a的定义,上面的累积,并没有考虑最后到的位置,即现在的u,v,所以还要算上自己,但由于u,v一毛一样,所以只累积一次。
*/
for(int i=17;i>=0;i--){
if(f[u][i]!=f[v][i]){
now=UP(now,a[u][i]);
now=UP(now,a[v][i]);
u=f[u][i],v=f[v][i];
}
}
now=UP(now,a[u][0]);
now=UP(now,a[v][1]);
/*
注意一点,平时我们求LCA时,最后return的是 fa[u][0];
也就是说现在这两点还没跳到lca上
即此时他们而是分别是lca的两个直系儿子,(为什么呢:因为如果他们能跳到同一个节点上必然其中一个是另一个的祖先,而这种情况上面已经讨论过了)
那么现在还要累计上lca,nowu,nowv,因为根据a的定义,上面的累积并没有考虑到这三点,所以都要加上,
即加上a[u][0]+a[v][0]+a[lca][0]
注意到,a[v][0]+a[lca][0]=a[v][1]
所以我们直接UP(a[v][1])就好了。。
*/
}
int main(){
int n,m,q;
scanf("%d%d%d",&n,&m,&q);
for(int i=1,u,v;i<n;i++){
scanf("%d%d",&u,&v);
e[u].pb(v),e[v].pb(u);
}
for(int i=1,u;i<=m;i++){
scanf("%d",&u);
city[u].pb(i);
}//i住在城市u
dfs(1,0,1);
for(int j=2;j<=17;j++)
for(int i=1;i<=n;i++){
f[i][j]=f[f[i][j-1]][j-1];
a[i][j]=UP(a[i][j-1],a[f[i][j-1]][j-1]);
}//倍增预处理,同时也把a数组处理了
for(int i=1,u,v,p;i<=q;i++){
scanf("%d%d%d",&u,&v,&p);
LCA(u,v);//u到v的路径上求前p小的
int len=now.size();
p=min(p,len);
printf("%d",p);
for(int j=0;j<p;j++)printf(" %d",now[j]);puts("");
}
}