传送门

•参考资料

  [1]:ACM-ICPC 2018 青岛赛区网络预赛 B. Red Black Tree (LCA、二分)

•题意 

  给出一棵树,根节点为1。

  每条边有一个权值,树上有红色结点 m 个,其花费为 0 ,其余为黑色;

  每个黑色结点的花费为其到最近红色祖先的经过的路径权值之和。

  有 q 次询问,每次给出一个点集;

  问将树上任意一个结点涂成红色结点后,点集中所有点的花费的最大值的最小是多少。

•题解

  相关变量解释:

    sum : 每次询问中询问的点集个数

    a[  ]  : 存储每次询问到的点集

    costR[i] : 结点 i 距其最近红色祖先的花费

  预处理每个点到根的距离cost、到最近红色祖先的距离 costR 和 ST 表。

  对于每次询问,将a[ ] 按 costR 从大到小排序,在 0~costR[a[0]] 范围内二分答案;

  对所有大于答案的点求它们的公共祖先(利用ST表可以O(1)求两点的公共祖先),将其涂红;

  之后计算每个大于答案的点的新花费是否小于答案。

•Code

 #include<iostream>
#include<vector>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
#define pb push_back
#define ll long long
#define mem(a,b) (memset(a,b,sizeof a))
const int maxn=1e5+; int n,m,q;
//===============Restore Graph============
struct Node
{
int to;
ll w;
Node(int to,int w):to(to),w(w){}
};
vector<Node >G[maxn];
void addEdge(int u,int v,int w)
{
G[u].pb(Node(v,w));
G[v].pb(Node(u,w));
}
//=========================================
int vs[*maxn];//欧拉序列,范围区间为 [1,total]
int depth[*maxn];//欧拉序列对应的深度序列
int pos[maxn];//pos[i] : 结点 i 再欧拉序列中第一次出现的位置
ll cost[maxn];//cost[i] : 结点 i 距根据点的距离
ll costR[maxn];//costR[i] : 结点 i 距最近红色祖先结点的距离,初始化为 -1
int total;//欧拉序列的大小
void dfs(int u,int f,int dep,ll dis)
{
vs[++total]=u;
depth[total]=dep;
pos[u]=total;
cost[u]=dis;
for(int i=;i < G[u].size();++i)
{
Node e=G[u][i];
if (e.to == f)
continue;
costR[e.to]=(costR[e.to] == ? :costR[u]+e.w);
dfs(e.to,u,dep+,dis+e.w);
vs[++total]=u;
depth[total]=dep;
}
}
//==================RMQ======================
struct Node2
{
int mm[ * maxn];
int dp[ * maxn][];
void ST()
{
int n=total;
mm[] = -;
for (int i = ; i <= n; i++)
{
mm[i]=((i&(i-))==) ? mm[i - ] + :mm[i - ];
dp[i][]=i;
}
for (int j=;j <= mm[n];j++)
for (int i=;i+(<<j)- <= n;i++)
if(depth[dp[i][j - ]] < depth[dp[i+(<<(j-))][j-]])
dp[i][j]=dp[i][j-];
else
dp[i][j]=dp[i+(<<(j-))][j-];
}
int Lca(int u, int v)
{
u=pos[u],v=pos[v];
if (u > v)
swap(u, v);
int k = mm[v-u+];
if(depth[dp[u][k]] <= depth[dp[v-(<<k)+][k]])
return vs[dp[u][k]];
return vs[dp[v-(<<k)+][k]];
}
}_rmq;
//==========================================
int a[maxn];
int sum;
bool cmp(int a, int b)
{
return costR[a] > costR[b];
}
bool Check(ll x)
{
if(costR[a[]] <= x)
return true;
int lca=a[];
for(int i=;i < sum;i++)
{
if(costR[a[i]] <= x)
break;
lca=_rmq.Lca(lca,a[i]);
}
for(int i = ;i < sum;i++)
{
if(costR[a[i]] <= x)
return true;
if(cost[a[i]]-cost[lca] > x)
return false;
}
return true;
}
void Solve()
{
dfs(,-,,);
_rmq.ST();
while(q--)
{
scanf("%d",&sum);
for (int i=;i < sum; i++)
scanf("%d",&a[i]);
sort(a,a+sum,cmp);
ll l=,r=costR[a[]];
while(l < r)
{
ll mid=(l+r)/;
if(Check(mid))
r=mid;
else
l=mid + ;
}
printf("%lld\n",l);
}
}
void init()
{
mem(costR,-);
total=;
for(int i=;i < maxn;++i)
G[i].clear();
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
init();
scanf("%d%d%d",&n,&m,&q);
while(m--)
{
int red;
scanf("%d",&red);
costR[red]=;
}
costR[]=;
for(int i=;i<n;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addEdge(u,v,w);
}
Solve();
}
return ;
}

•出现的问题

  1、用 vector 存储图比用 链式前向星存储图要慢

    (1)vector :

    (2)链式前向星:

  2、平常一直在用的RMQ会超时

 //=====================RMQ===================
struct Node1
{
int dp[][*maxn];
void Preset()
{
for(int i=;i < *maxn;++i)
dp[][i]=i;
}
void ST()
{
int k=log(total)/log();
for(int i=;i <= k;++i)
for(int j=;j <= (total-(<<i)+);++j)
if(depth[dp[i-][j]] > depth[dp[i-][j+(<<(i-))]])
dp[i][j]=dp[i-][j+(<<(i-))];
else
dp[i][j]=dp[i-][j];
}
int Lca(int u,int v)
{
u=pos[u],v=pos[v];
if(u > v)
swap(u,v);
int k=log(v-u+)/log();
if(depth[dp[k][u]] > depth[dp[k][v-(<<k)+]])
return vs[dp[k][v-(<<k)+]];
return vs[dp[k][u]];
}
}_rmq;
//===========================================

TLE

 //==================RMQ======================
struct Node2
{
int mm[ * maxn];
int dp[ * maxn][];
void ST()
{
int n=total;
mm[] = -;
for (int i = ; i <= n; i++)
{
mm[i]=((i&(i-))==) ? mm[i - ] + :mm[i - ];
dp[i][]=i;
}
for (int j=;j <= mm[n];j++)
for (int i=;i+(<<j)- <= n;i++)
if(depth[dp[i][j - ]] < depth[dp[i+(<<(j-))][j-]])
dp[i][j]=dp[i][j-];
else
dp[i][j]=dp[i+(<<(j-))][j-];
}
int Lca(int u, int v)
{
u=pos[u],v=pos[v];
if (u > v)
swap(u, v);
int k = mm[v-u+];
if(depth[dp[u][k]] <= depth[dp[v-(<<k)+][k]])
return vs[dp[u][k]];
return vs[dp[v-(<<k)+][k]];
}
}_rmq;
//==========================================

AC

  3、cost[ ] 很有用,如果 Check( ) 中不加    

      if(cost[a[i]]-cost[lca] > x)
        return false;

    会返回 WA,具体为什么,明天再好好想想%%%%%%%%%

 


分割线:2019.5.8

  中石油的这场重现赛又让我回想起了这道题留下的疑惑;

  现在再想想这道题,思路清晰了些许;

  一些不理解的地方瞬间顿悟了;

  ST表处理RMQ中,会多次求解 log2(x),这种算式是比较耗时的,我们预处理出所需的log2(x);

logTwo[i]=log2(i);

  如何预处理呢?

  首先想一下,三位数的二进制数的最大值为 111(2),四位数的二进制数的最小值为 1000(2)

  两者的关系是 (111)&(1000) = 0 , 而对于任意三位二进制数 x,y ,(x&y) != 0;

  有了这个关系后,就可以这么预处理了:

logTwo[]=-;
for(int i=;i <= n;++i)
logTwo[i]=(i&(i-)) == ? logTwo[i-]+:logTwo[i-];

  这就是之前一直不理解的ST表加速的地方;

•Code

 #include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
#define INFll 0x3f3f3f3f3f3f3f3f
const int maxn=1e5+; int n,m,q;
ll C[maxn];///C[i]:节点i到根节点1的花费
ll CR[maxn];///CR[i]:节点i到其最近的红色祖先节点的花费
int num;
int head[maxn];
struct Edge
{
int to;
ll w;
int next;
}G[maxn<<];
void addEdge(int u,int v,ll w)
{
G[num]={v,w,head[u]};
head[u]=num++;
}
struct LCA
{
int vs[maxn<<];///欧拉序列
int dep[maxn<<];///欧拉序列中的节点对应的深度序列
int pos[maxn<<];///pos[i]:节点i在欧拉序列中第一次出现的位置
int cnt;
int logTwo[maxn<<];///logTwo[i]:log2(i)
int dp[maxn<<][];///dp[i][j]:[i,i+2^j-1]深度最小的点的下标(欧拉序列中的下标)
void DFS(int u,int f,int depth,ll dist)
{
vs[++cnt]=u;
dep[cnt]=depth;
pos[u]=cnt;
C[u]=dist;
for(int i=head[u];~i;i=G[i].next)
{
int v=G[i].to;
ll w=G[i].w;
if(v == f)
continue;
CR[v]=min(CR[v],CR[u]+w);
DFS(v,u,depth+,dist+w);
vs[++cnt]=u;
dep[cnt]=depth;
}
}
void ST()
{
logTwo[]=-;
for(int i=;i <= cnt;++i)
{
dp[i][]=i;
///:后的语句写错了,刚开始写成了logTwo[i],debug了好一会
logTwo[i]=(i&(i-)) == ? logTwo[i-]+:logTwo[i-];
}
for(int k=;k <= logTwo[cnt];++k)
for(int i=;i+(<<k)- <= cnt;++i)
if(dep[dp[i][k-]] > dep[dp[i+(<<(k-))][k-]])
dp[i][k]=dp[i+(<<(k-))][k-];
else
dp[i][k]=dp[i][k-];
}
void lcaInit(int root)
{
cnt=;
DFS(root,root,,);
ST();
}
int lca(int u,int v)///返回节点u,v的LCA
{
u=pos[u];
v=pos[v]; if(u > v)
swap(u,v); int k=logTwo[v-u+];
if(dep[dp[u][k]] > dep[dp[v-(<<k)+][k]])
return vs[dp[v-(<<k)+][k]];
else
return vs[dp[u][k]];
}
}_lca; int qCnt;
int query[maxn<<]; bool Check(ll mid)
{
int lca=;///不满足条件的点的LCA
for(int i=;i <= qCnt;++i)
{
if(CR[query[i]] <= mid)
continue;
if(lca == )
lca=query[i];
else/// > mid的点LCA
lca=_lca.lca(lca,query[i]);
} for(int i=;i <= qCnt;++i)
{
if(CR[query[i]] <= mid)
continue; ///如果将lca点涂红后还不能使其 <= mid,返回false
if(C[query[i]]-C[lca] > mid)
return false;
}
return true;
}
void Solve()
{
_lca.lcaInit(); for(int i=;i <= q;++i)
{
scanf("%d",&qCnt); ll l=-,r=;
for(int j=;j <= qCnt;++j)
{
scanf("%d",query+j);
r=max(r,CR[query[j]]);
} while(r-l > )
{
ll mid=l+((r-l)>>);
if(Check(mid))
r=mid;
else
l=mid;
}
printf("%lld\n",r);
}
}
void Init()
{
num=;
mem(head,-);
mem(CR,INFll);///初始化为最大值
}
int main()
{
// freopen("C:\\Users\\hyacinthLJP\\Desktop\\in&&out\\contest","r",stdin);
int test;
scanf("%d",&test);
while(test--)
{
Init();
scanf("%d%d%d",&n,&m,&q);
for(int i=;i <= m;++i)
{
int red;
scanf("%d",&red);
CR[red]=;
}
CR[]=;
for(int i=;i < n;++i)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addEdge(u,v,w);
addEdge(v,u,w);
}
Solve();
}
return ;
}

2018.9青岛网络预选赛(B)的更多相关文章

  1. 2018.9青岛网络预选赛(K)

    传送门:Problem K https://www.cnblogs.com/violet-acmer/p/9664805.html 题意: 给你n个数,找出满足条件的最多的数的个数. 题解: 满足条件 ...

  2. 2018.9青岛网络预选赛(A)

    传送门:Problem A https://www.cnblogs.com/violet-acmer/p/9664805.html 题意: 求m个PERFECTs中最多有多少个连续的PERFECT和最 ...

  3. 2018.9青岛网络预选赛(C)

    传送门:Problem C https://www.cnblogs.com/violet-acmer/p/9664805.html 题意: 定义五个指令,判断能否从输入的n条指令中成功跳出循环,如果不 ...

  4. 2018.9青岛网络预选赛(J)

    传送门:Problem J https://www.cnblogs.com/violet-acmer/p/9664805.html 题目大意: BaoBao和DreamGrid玩游戏,轮流按灯的按钮, ...

  5. 2018.9青岛网络预选赛(H)

    传送门:Problem H https://www.cnblogs.com/violet-acmer/p/9664805.html 题意: BaoBao在一条有刻度的路上行走(哈哈,搞笑),范围为 [ ...

  6. The 2018 ACM-ICPC Asia Qingdao Regional Contest, Online(2018 青岛网络预选赛)

    A题 A Live Love 原题链接:https://pintia.cn/problem-sets/1036903825309761536/problems/1041155943483625472 ...

  7. 2018 icpc 青岛网络赛 J.Press the Button

    Press the Button Time Limit: 1 Second      Memory Limit: 131072 KB BaoBao and DreamGrid are playing ...

  8. 2018 ICPC青岛网络赛 B. Red Black Tree(倍增lca好题)

    BaoBao has just found a rooted tree with n vertices and (n-1) weighted edges in his backyard. Among ...

  9. 2018.9南京网络预选赛(J)

    传送门:Problem J https://www.cnblogs.com/violet-acmer/p/9720603.html 变量解释: need[ i ] : 第 i 个房间含有的旧灯泡个数. ...

随机推荐

  1. git-两小时简单使用教程

    两小时学会Git玩转Github 1. 了解Git和Github 1.1什么是Git Git是一个免费.开源的版本控制软件 1.2什么是版本控制系统 版本控制是一种记录一个或若干个文件内容变化,以便将 ...

  2. c++入门之关于cin,cout以及数据流的认识

  3. 一些调格式的经验 & 插入图注和尾注

    一些调格式的经验(以Word2010为例) 1. 从目录正文分别编页码 将光标放在要重新编写页码起始页的最开始位置 分节:页面布局->分隔符->分节符(连续) 插入页码后,选中页码起始页页 ...

  4. php开启curl不成功原因

    1. 在php.ini中找到 ;extension=php_curl.dll, 如果前面有分号, 去掉 2. php_curl.dll (ext目录下, 如果没有, 请下载) , libeay32.d ...

  5. 兼容IE-FireFox-Chrome的背景音乐播放

    以music目录下的kn.mp3文件为例: <bgsound src="music/kn.mp3" loop="-1"/> <audio sr ...

  6. vue 路由传参 params 与 query两种方式的区别

    初学vue的时候,不知道如何在方法中跳转界面并传参,百度过后,了解到两种方式,params 与 query.然后,错误就这么来了:  router文件下index.js里面,是这么定义路由的: { p ...

  7. [转帖]知乎专栏:正确使用 Docker 搭建 GitLab 只要半分钟

    正确使用 Docker 搭建 GitLab 只要半分钟 https://zhuanlan.zhihu.com/p/49499229 很多程序员在内网搭建 gitlab 都搭建的坑坑洼洼,不支持 htt ...

  8. CSS 居中(拿来主义自用)

    居中是我们使用css来布局时常遇到的情况.使用css来进行居中时,有时一个属性就能搞定,有时则需要一定的技巧才能兼容到所有浏览器,本文就居中的一些常用方法做个简单的介绍. 注:本文所讲方法除了特别说明 ...

  9. Bootstrap @Media分类

      手机的屏幕比较小,宽度通常在600像素以下:PC的屏幕宽度,一般都在1000像素以上(目前主流宽度是1366×768)设置相应的min-width和max-width值 所以响应式设计一般对600 ...

  10. 软件工程_5th weeks

    从周五开始经历了清明小长假,周六.周日和周一都处于假期状态,因此对于上篇博客的评论回复的很迟,而这周的工作做的也不多. 1.爆打小组 4.5日站立会议 时间:2016.4.5 15:00-15:34 ...