tarjan+DAG 上的 dp

难点在于建图和连边,其实也不难,就是细节挺恶心

我和正解对拍拍出来 3 个错误。。。

传送门:luogu

bzoj

题目描述

有座宫殿呈矩阵状,由 \(R\times C\) 间矩形宫室组成,其中有 \(N\) 间宫室里埋藏着宝藏,称作藏宝宫室。宫殿里外、相邻宫室间都由坚硬的实体墙阻隔,由一间宫室到达另一间只能通过传送门。这 \(N\) 间藏宝宫室每间都架设了一扇传送门,没有宝藏的宫室不设传送门,所有的宫室传送门分为三种:

  1. “横天门”:由该门可以传送到同行的任一宫室;
  2. “纵寰门”:由该门可以传送到同列的任一宫室;
  3. “任意门”:由该门可以传送到以该门所在宫室为中心周围 \(8\) 格中任一宫室(如果目标宫室存在的话)。

初始时,可以由任意一间藏宝宫室进入,并由任意一间藏宝宫室离开,但只能进入离开一次

输入格式

第一行给出三个正整数 \(N,R,C\)。

以下 \(N\) 行,每行给出一扇传送门的信息,包含三个正整数 \(x_i, y_i, T_i\),表示该传送门设在位于第 \(x_i\) 行第 \(y_i\) 列的藏宝宫室,类型为 \(T_i\)。\(T_i\) 是一个 \([1,3]\) 间的整数,\(1\) 表示可以传送到第 \(x_i\) 行任意一列的“横天门”,\(2\) 表示可以传送到任意一行第 \(y_i\) 列的“纵寰门”,\(3\) 表示可以传送到周围 \(8\) 格宫室的“任意门”。

保证 \(1\le x_i\le R,1\le y_i\le C\),所有的传送门位置互不相同。

输出格式

只有一个正整数,表示你确定的路线所经过不同藏宝宫室的最大数目。


对于每一个强连通分量,只要到达它中的一个点,剩下的点就都可以到达

所以我们只要 tarjan 缩点以后,按照每个强连通分量间的边的关系,重新连边,然后这个就是一个 DAG

这个 DAG 上的每个点的点权,就可以理解为对应的强连通分量的大小,也就是走到这个强连通分量能对答案产生多大贡献

然后做个简单的 dp 就好了,用 \(f_i\) 表示第 \(i\) 个点(DAG 上的)结尾,最多可以产生多大的答案

最终答案就是 \(\max_{i=1}^{scccnt} f_i\),其中,\(scccnt\) 当然就是强连通分量的个数

同时也是新建的 DAG 的点数


现在考虑如何连边,直接连肯定T飞

对于每一行,所以类型为 \(1\) 的点(横着的门),可以连一个环,这样保证了从任意一个横着的门进入,都能去往同一行的其它所有类型为 \(1\) 的门

然后对于类型不是 \(1\) 的门,随便找一个类型是 \(1\) 的门,向它们连边,保证了从任意一个横着的门,都可以去往同一行中,不是类型 \(1\) 的门

就符合要求了

具体实现要先对所以门排序,然后按照每一行枚举

细节比较多,具体看代码中的注释

那么对于每一列也是如此

对于那种“任意门”更简单那了,用 map 记录每个位置是不是藏宝宫室,如果是就连边就行了

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<utility>
#include<iomanip>
#include<queue>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
register int x=0;register int y=1;
register char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
return y?x:-x;
}
int n;
#define N 100006
#define M 1000006
struct data{
int x,y,id,type;
}p[N];
int have_1[1000006],have_2[1000006];
int fir[N],nex[M],to[M],tot;
int fir_[N],nex_[M],to_[M],tot_;
std::map<std::pair<int,int>,int>map;
int dfn[N],low[N],dfscnt;
int scc[N],size[N],scccnt;
int stack[N],top;
int in[N];
void debug(){
int a;
return;
}
inline void add(int u,int v){
if(u==4&&v==1) debug();
to[++tot]=v;
nex[tot]=fir[u];fir[u]=tot;
}
inline void add_(int u,int v){
to_[++tot_]=v;
nex_[tot_]=fir_[u];fir_[u]=tot_;
}
inline int cmpx(data x,data y){
if(x.x==y.x) return x.type<y.type;//横在前
return x.x<y.x;
}
inline int cmpy(data x,data y){
if(x.y==y.y) return x.type>y.type;
return x.y<y.y;
}
void tarjan(int u){
dfn[u]=low[u]=++dfscnt;stack[top++]=u;
for(reg int v,i=fir[u];i;i=nex[i]){
v=to[i];
if(!dfn[v]){
tarjan(v);low[u]=std::min(low[u],low[v]);
}
else if(!scc[v]) low[u]=std::min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
scccnt++;
do{
size[scccnt]++;scc[stack[--top]]=scccnt;
}while(stack[top]!=u);
}
}
inline void build(){
std::sort(p+1,p+1+n,cmpx);
for(reg int i=1;i<=n;){
if(!have_1[p[i].x]){
//如果这一行没有 1 类型的门(横的门),那么这一行内肯定不会有连边
//这里如果不特判后面会出问题,在这一行一个 1 的门都没有的情况下
int now_x=p[i].x;
for(i++;p[i].x==now_x;i++);
continue;
}
int now_x=p[i].x,now=p[i].id;
int last_i=i;//last_i 是环中第一个点,同时我门也选这个点,向其它类型不是 1 的门连边
for(i++;p[i].x==now_x&&p[i].type==1&&i<=n;i++) add(p[i-1].id,p[i].id);//横的连成环
add(p[i-1].id,p[last_i].id);//连回去,才能构成一个环
for(;p[i].x==now_x&&i<=n;i++) add(now,p[i].id);
}
std::sort(p+1,p+1+n,cmpy);
for(reg int i=1;i<=n;){
if(!have_2[p[i].y]){//同理
int now_y=p[i].y;
for(i++;p[i].y==now_y;i++);
continue;
}
int now_y=p[i].y,tmp=i;//记录这一列是从哪标号开始的
for(;p[i].y==now_y&&i<=n&&p[i].type==3;i++);
int now=p[i].id,last_i=i;//last_i 是环中第一个点,now 即为我门选取的那个类型为 2 的门,从他向其它类型不为 2 的门连边
for(i++;p[i].y==now_y&&i<=n&&p[i].type==2;i++) add(p[i-1].id,p[i].id);//同理,纵的连成环
add(p[i-1].id,p[last_i].id);//连回去,才能构成一个环
for(;p[i].y==now_y&&i<=n;i++) add(now,p[i].id);
for(reg int j=tmp;p[j].y==now_y&&j<=n&&p[j].type==3;j++) add(now,p[j].id);
//上面一行这是类型是 3 的门,因为排序时把他们放在了最前面,所以先记录下起始点,要在确定了一个类型 2 的门以后再重新从起始点开始循环,连边
}
}
const int dx[8]={0,0,1,1,1,-1,-1,-1};
const int dy[8]={-1,1,-1,0,1,-1,0,1};
inline void build_8(){
std::pair<int,int>pair;
for(reg int i=1;i<=n;i++)if(p[i].type==3){
reg int x=p[i].x,y=p[i].y,id=p[i].id,x_,y_;
for(reg int k=0;k<8;k++){
x_=x+dx[k];y_=y+dy[k];
pair=std::make_pair(x_,y_);
if(map.find(pair)!=map.end()) add(id,map[pair]);
}
}
}
inline void rebuild(){
for(reg int i=1;i<=n;i++)
for(reg int j=fir[i];j;j=nex[j])if(scc[i]!=scc[to[j]])
add_(scc[i],scc[to[j]]),in[scc[to[j]]]++;
}
std::queue<int>q;
int f[100006];
inline void topo(){
for(reg int i=1;i<=scccnt;i++)if(!in[i])
q.push(i),f[i]=size[i];
reg int u,v;
while(!q.empty()){
u=q.front();q.pop();
for(reg int i=fir_[u];i;i=nex_[i]){
v=to_[i];
f[v]=std::max(f[v],f[u]);
if(!--in[v]) f[v]+=size[v],q.push(v);
}
}
}
int main(){
// std::freopen("1.in","r",stdin);
n=read();read();read();
for(reg int i=1;i<=n;i++){
p[i].x=read();p[i].y=read();p[i].type=read();p[i].id=i;
map[std::make_pair(p[i].x,p[i].y)]=i;
if(p[i].type==1) have_1[p[i].x]=1;
if(p[i].type==2) have_2[p[i].y]=1;
}
build();build_8();
for(reg int i=1;i<=n;i++)if(!dfn[i]) tarjan(i);
rebuild();
topo();
reg int ans=0;
for(reg int i=1;i<=scccnt;i++) ans=std::max(ans,f[i]);
std::printf("%d",ans);
// EN;EN;EN;
// for(reg int i=1;i<=n;i++){
// std::printf("%d : ",i);
// for(reg int j=fir[i];j;j=nex[j]) std::printf("%d ",to[j]);
// EN;
// }
// for(reg int i=1;i<=n;i++) std::printf("%d ",scc[i]);EN;
// std::puts("new : ");
// for(reg int i=1;i<=n;i++){
// std::printf("%d : ",i);
// for(reg int j=fir_[i];j;j=nex_[j]) std::printf("%d ",to_[j]);
// EN;
// }
return 0;
}

[bzoj1924]P2403 [SDOI2010]所驼门王的宝藏的更多相关文章

  1. Luogu P2403 [SDOI2010]所驼门王的宝藏

    比较显然的缩点+拓扑排序题,只不过要建虚点优化建边. 首先我们发现在一个SCC里的点都是可以一起对答案产生贡献的,因此先缩成DAG,然后拓扑找最长链. 但是我们发现这题最坏情况下边数会达到恐怖的\(O ...

  2. 洛咕 P2403 [SDOI2010]所驼门王的宝藏

    简单tarjan. 一行的横天门如果暴力连边会被卡成平方,所以只要相邻两个横天门连双向边,再随便选一个横天门向整行连边即可.纵寰门同理.ziyou门直接map暴力连边. 然后tarjan直接dp. / ...

  3. BZOJ 1924 && Luogu P2403 [SDOI2010]所驼门王的宝藏 恶心建图+缩点DP

    记住:map一定要这么用: if(mp[x[i]+dx[j]].find(y[i]+dy[j])!=mp[x[i]+dx[j]].end()) add(i,mp[x[i]+dx[j]][y[i]+dy ...

  4. 洛谷 P2403 [SDOI2010]所驼门王的宝藏 题解

    题目描述 分析 先放一张图便于理解 这一道题如果暴力建图会被卡成\(n^{2}\) 实际上,在我们暴力建图的时候,有很多边都是重复的 假如一行当中有许多横天门的话,我们就不必要把这一行当中的所有点和每 ...

  5. [BZOJ 1924][Sdoi2010]所驼门王的宝藏

    1924: [Sdoi2010]所驼门王的宝藏 Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 1285  Solved: 574[Submit][Sta ...

  6. 【题解】SDOI2010所驼门王的宝藏(强连通分量+优化建图)

    [题解]SDOI2010所驼门王的宝藏(强连通分量+优化建图) 最开始我想写线段树优化建图的说,数据结构学傻了233 虽然矩阵很大,但是没什么用,真正有用的是那些关键点 考虑关键点的类型: 横走型 竖 ...

  7. [SDOI2010]所驼门王的宝藏

    题目描述 在宽广的非洲荒漠中,生活着一群勤劳勇敢的羊驼家族.被族人恭称为"先知"的Alpaca L. Sotomon是这个家族的领袖,外人也称其为"所驼门王". ...

  8. [LuoguP2403][SDOI2010]所驼门王的宝藏

    题目描述 在宽广的非洲荒漠中,生活着一群勤劳勇敢的羊驼家族.被族人恭称为"先知"的Alpaca L. Sotomon是这个家族的领袖,外人也称其为"所驼门王". ...

  9. BZOJ 1924: [Sdoi2010]所驼门王的宝藏 【tarjan】

    Description 在宽广的非洲荒漠中,生活着一群勤劳勇敢的羊驼家族.被族人恭称为“先 知”的Alpaca L. Sotomon 是这个家族的领袖,外人也称其为“所驼门王”.所 驼门王毕生致力于维 ...

随机推荐

  1. 统计分析_集中趋势and离散程度

    1.数组的集中趋势-如何定义数组的中心 1.1 常用几下几个指标来描述一个数组的集中趋势 均值-算术平均数 . 中位数-将数组升序或降序排列后,位于中间的数. 众数-数组中出现最多的数. 1.2 指标 ...

  2. bit/byte/ascii/unicode

    bit(位).byte(字节).ASCII.Unicode 和 UTF-8位和字节的关系bit 电脑记忆体中最小的单位,在二进位电脑系统中,每一bit 可以代表0 或 1 的数位讯号byte一个byt ...

  3. Scala——的并行集合

    当出现Kafka单个分区数据量很大,但每个分区的数据量很平均的情况时,我们往往采用下面两种方案增加并行度: l  增加Kafka分区数量 l  对拉取过来的数据执行repartition 但是针对这种 ...

  4. stand up meeting 11/19/2015

    队员 今日工作 工作耗时/h 明日计划 计划耗时/h 冯晓云 利用昨天编写的调用必应词典API的DLL,完成了UWP版本查词APP的试水,证实了DLL可调和在线查词的可行性:和其他部分的同学就接口数据 ...

  5. PrestoSPI安全扩展

    由于Presto官方文档和谷歌搜索都没有相关的内容,git项目中也没有支持sentry的安全插件扩展,因此只能从源码中寻找答案,在梳理完SPI包的安全相关源码结构后,已实现了一个自定义的安全插件,经验 ...

  6. 联通友华通信光纤猫PT952G设置无线路由光猫桥接拨号

    #0x1 登陆后台,点击网络,点击宽带设置.选择第二个接口. 0x2 只修改模式,改成Bridge,其他无需修改.然后直接接路由器拨号就行,或者电脑都行. 0x4  恢复默认拨号,这样修改以后,直接连 ...

  7. SpringCloud-Ribbon负载均衡机制、手写轮询算法

    Ribbon 内置的负载均衡规则 在 com.netflix.loadbalancer 包下有一个接口 IRule,它可以根据特定的算法从服务列表中选取一个要访问的服务,默认使用的是「轮询机制」 Ro ...

  8. asp.net core webapi 配置跨域处理

    在Startup.cs文件中的ConfigureServices方法中加入如下代码: //配置跨域处理 services.AddCors(options => { options.AddPoli ...

  9. 记录在腾讯云上搭建Ubuntu服务器

    为了能让更多的比赛题复现,只好自己去手动搭建服务器 各种奇葩的操作以及很多的由于升级之后出现的问题变成了一个个坑. 写下这篇博客以此来记录我踩过的坑. 第一步 购买一个服务器,当然我购买的是学生版本的 ...

  10. tp5 -- join注意事项

    使用数据库关联查询的时候,有时候会避免不了两个表格字段名称都一样的尴尬, 这时候管理查询出来的只有其中一个表格字段名称的数据,因为在相同字段名称的情况下,数据会自动覆盖. 这时候,我们只需要给其中一个 ...