zoukankan      html  css  js  c++  java
  • [bzoj1924]P2403 [SDOI2010]所驼门王的宝藏

    tarjan+DAG 上的 dp
    难点在于建图和连边,其实也不难,就是细节挺恶心
    我和正解对拍拍出来 3 个错误。。。

    传送门:luogu
    bzoj

    题目描述

    有座宫殿呈矩阵状,由 (R imes 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) 格宫室的“任意门”。

    保证 (1le x_ile R,1le y_ile 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;
    }
    
  • 相关阅读:
    python第七十九天--第十四周作业
    python第七十七天---HTML
    python第七十六天--堡垒机完成
    python第七十一天---堡垒机
    python第六十八天--第十二周作业
    XmlHepler(拿去就能用)
    .NET中代理服务器WebProxy的各种用法
    XML VS DataSet
    C#操作XML方式
    C#读取XML方式
  • 原文地址:https://www.cnblogs.com/suxxsfe/p/12727803.html
Copyright © 2011-2022 走看看