Libre OJ 2255 (线段树优化建图+Tarjan缩点+DP)
题面
分析
主体思路:若x能引爆y,从x向y连一条有向边,最后的答案就是从x出发能够到达的点的个数
首先我们发现一个炸弹可以波及到的范围一定是坐标轴上的一段连续区间
我们可以用二分查找求出炸弹能波及到最左边和最右边的点,记为[l,r]
然后我们就需要向编号属于区间[l,r]的点连一条有向边
如果直接连边,时间复杂度为\(O(n^2)\) 无法接受,考虑用线段树优化连边
我们将线段树看成一个有向图,每个线段树节点看成图上的一个点,[l,r]向[l,mid],[mid+1,r]连边,叶子节点[l,l]向原图上的节点l连边
对于从x向编号属于区间[L,R]的点连边,我们用类似线段树区间更新的方法,将[L,R]拆成许多个小区间,再直接向这些小区间暴力连边

根据线段树的性质,最多会分出\(\left[ \log _{2}n\right]\)个节点,所以单次连边的时间复杂度为\(O(\log n)\)
然后就很套路了,显然环上的点可以缩成一个大点(权值为环上所有节点权值之和(线段树节点权值为0,原图上节点权值为1))
Tarjan完在DAG上DP即可
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<stack>
#include<queue>
#include<vector>
#define maxn 1700005
#define mod 1000000007
using namespace std;
inline void qread(int &x) {
x=0;
int sign=1;
char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') sign=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
x=x*sign;
}
inline void qread(long long &x) {
x=0;
long long sign=1;
char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') sign=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
x=x*sign;
}
int n;
long long x[maxn];
long long r[maxn];
struct edge {
int from;
int to;
edge() {
}
edge(int u,int v) {
from=u;
to=v;
}
friend bool operator == (edge a,edge b) {
return a.to==b.to&&a.from==b.from;
}
friend bool operator < (edge a,edge b) {
if(a.from==b.from) return a.to<b.to;
else return a.from<b.from;
}
};
set<edge>vis1;
set<edge>vis2;
vector<int>G[maxn],D[maxn];
int w[maxn];
void add_edge(int u,int v) {
G[u].push_back(v);
}
int newn=n;
struct node {
int l;
int r;
} tree[maxn];
void build(int l,int r,int pos) {
newn++;
tree[pos].l=l;
tree[pos].r=r;
if(l==r) {
add_edge(pos+n,l);
return;
}
add_edge(pos+n,pos*2+n);
add_edge(pos+n,pos*2+1+n);
int mid=(l+r)>>1;
build(l,mid,pos<<1);
build(mid+1,r,pos<<1|1);
}
void update(int L,int R,int v,int pos) {
if(L<=tree[pos].l&&R>=tree[pos].r) {
add_edge(v,pos+n);
return;
}
int mid=(tree[pos].l+tree[pos].r)>>1;
if(L<=mid) update(L,R,v,pos<<1);
if(R>mid) update(L,R,v,pos<<1|1);
}
stack<int>s;
int tim=0;
int m=0;
int ins[maxn];
int dfn[maxn];
int low[maxn];
int belong[maxn];
int sz[maxn];
void tarjan(int x) {
s.push(x);
ins[x]=1;
dfn[x]=low[x]=++tim;
int tmp=G[x].size();
for(int i=0; i<tmp; i++) {
int y=G[x][i];
if(!dfn[y]) {
tarjan(y);
low[x]=min(low[x],low[y]);
} else if(ins[y]) {
low[x]=min(low[x],dfn[y]);
}
}
if(low[x]==dfn[x]) {
m++;
int y;
do {
y=s.top();
s.pop();
ins[y]=0;
belong[y]=m;
sz[m]+=w[y];
} while(x!=y);
}
}
void dcg_to_dag() {
for(int i=1; i<=n; i++) {
if(!dfn[i]) tarjan(i);
}
int s;
for(int i=1; i<=n; i++) {
s=G[i].size();
for(int j=0; j<s; j++) {
if(belong[i]!=belong[G[i][j]]&&!vis2.count(edge(belong[i],belong[G[i][j]]))) {
vis2.insert(edge(belong[i],belong[G[i][j]]));
D[belong[i]].push_back(belong[G[i][j]]);
}
}
}
}
long long dp[maxn];
int dfs(int x){
if(dp[x]) return dp[x];
dp[x]=sz[x];
int tmp=D[x].size();
for(int i=0;i<tmp;i++){
int y=D[x][i];
dp[x]+=dfs(y);
}
return dp[x];
}
long long solve() {
long long ans=0;
for(int i=1;i<=n;i++){
if(!dp[i]) dfs(i);
}
for(int i=1; i<=n; i++) {
ans=(ans+w[i]*i*dp[belong[i]]%mod)%mod;
}
return ans;
}
int main() {
int L,R;
qread(n);
for(int i=1; i<=n; i++) {
w[i]=1;
qread(x[i]);
qread(r[i]);
}
newn=n;
build(1,n,1);
for(int i=1; i<=n; i++) {
L=lower_bound(x+1,x+1+n,x[i]-r[i])-x;
R=upper_bound(x+1,x+1+n,x[i]+r[i])-x-1;
update(L,R,i,1);
}
n=newn;
dcg_to_dag();
printf("%lld\n",solve());
}
Libre OJ 2255 (线段树优化建图+Tarjan缩点+DP)的更多相关文章
- 【2019.7.26 NOIP模拟赛 T3】化学反应(reaction)(线段树优化建图+Tarjan缩点+拓扑排序)
题意转化 考虑我们对于每一对激活关系建一条有向边,则对于每一个点,其答案就是其所能到达的点数. 于是,这个问题就被我们搬到了图上,成了一个图论题. 优化建图 考虑我们每次需要将一个区间向一个区间连边. ...
- bzoj5017 [Snoi2017]炸弹 (线段树优化建图+)tarjan 缩点+拓扑排序
题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=5017 题解 这个题目方法挺多的. 线段树优化建图 线段树优化建图的做法应该挺显然的,一个炸弹能 ...
- bzoj5017 炸弹 (线段树优化建图+tarjan+拓扑序dp)
直接建图边数太多,用线段树优化一下 然后缩点,记下来每个点里有多少个炸弹 然后按拓扑序反向dp一下就行了 #include<bits/stdc++.h> #define pa pair&l ...
- 【bzoj5017】[Snoi2017]炸弹 线段树优化建图+Tarjan+拓扑排序
题目描述 在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸时,如果另一个炸弹所在位置 Xj 满足: Xi−Ri≤Xj≤Xi+Ri,那么,该炸弹也会被引爆. 现在 ...
- 炸弹:线段树优化建边+tarjan缩点+建反边+跑拓扑
这道题我做了有半个月了...终于A了... 有图为证 一句话题解:二分LR线段树优化建边+tarjan缩点+建反边+跑拓扑统计答案 首先我们根据题意,判断出来要炸弹可以连着炸,就是这个炸弹能炸到的可以 ...
- BZOJ5017 [SNOI2017]炸弹 - 线段树优化建图+Tarjan
Solution 一个点向一个区间内的所有点连边, 可以用线段树优化建图来优化 : 前置技能传送门 然后就得到一个有向图, 一个联通块内的炸弹可以互相引爆, 所以进行缩点变成$DAG$ 然后拓扑排序. ...
- 『炸弹 线段树优化建图 Tarjan』
炸弹(SNOI2017) Description 在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸 时,如果另一个炸弹所在位置 Xj 满足: Xi−Ri≤Xj≤Xi ...
- 【2019北京集训2】duck 线段树优化建图+tarjan
题目大意:给你$n$个点,第$i$个点有点权$v_i$.你需要将这$n$个点排成一排,第$i$个点的点权能被累加当且仅当这个点前面存在编号在$[l_i,r_i]$中的点,问你这些点应该如何排列,点权和 ...
- BZOJ5017 炸弹(线段树优化建图+Tarjan+拓扑)
Description 在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸时,如果另一个炸弹所在位置 Xj 满足: Xi−Ri≤Xj≤Xi+Ri,那么,该炸弹也会被 ...
随机推荐
- js中的函数声明置顶
函数声明置顶是指 js引擎在读取变量与声明式函数时,会优先读取,例如如下 var a = 1: function a(){}; console.log(a); //这里得到的为1,而不是该functi ...
- Rsync安装部署
Rsync安装部署 1.Rsync 简介 Rsync 是一款开源的.快速的 多功能的 可以实现全量以及增量的本地或者是远程的数据同步备份的优秀工具,并且可以不进行改变原有的数据属性信息,实现数据的 ...
- tree 数状型结构显示目录下的内容
1. 命令功能 tree中文意思“树”,以树形结构显示目录内容.. 2. 语法格式 tree [option] [directory] tree 选项 目录 3. 使用范例 当最小化安装l ...
- 51nod 1554 欧姆诺姆和项链
有一天,欧姆诺姆发现了一串长度为n的宝石串,上面有五颜六色的宝石.他决定摘取前面若干个宝石来做成一个漂亮的项链. 他对漂亮的项链是这样定义的,现在有一条项链S,当S=A+B+A+B+A+...+A+B ...
- CSP-S2019 赛前补题
前言 该打的比赛也打完了,每一场打得并不是很理想,所以就没写赛后总结了.最后再把每一场的比赛补一下,也算给自己一个交代吧. 牛客CSP-S提高组赛前集训营6 考试 100 + 30 + 0 = 130 ...
- 三栏布局的三个典型方法(圣杯、双飞翼、flex)
聊聊三栏布局----左右定宽,中间自适应. 效果图: 圣杯布局 <!DOCTYPE html> <html> <head lang="en"> ...
- LinkedList与ArrayList的区别(内部实现)
ArrayList的内部实现是基于内部数组Object[],所以从概念上讲,它更像数组: LinkedList的内部实现是基于一组连接的记录,所以,它更像一个链表结构,所以,它们在性能上有很大的差别. ...
- TPS、QPS和系统吞吐量的区别和理解
参考:https://blog.csdn.net/u010889616/article/details/83245695 一.QPS/TPSQPS:Queries Per Second意思是“每秒查询 ...
- RedHat Linux6.4下安装apache服务
一.换yum 原因:安装apache2.4是需要安装apr . apr-util .pcre.httpd四个包, 在安装pcre包时会报错: configure: error: You need a ...
- stack1顺序栈
顺序栈 #include<iostream> using namespace std; #define increasesize 10 template <class Object& ...