首页 > 其他分享 >TJ - 「ZJOI2011」道馆之战

TJ - 「ZJOI2011」道馆之战

时间:2024-03-03 19:11:37浏览次数:32  
标签:int ans1 id ZJOI2011 TJ rans lans 道馆 dis

「ZJOI2011」道馆之战

难度:2500

\(1s,256MB\)

一,题目:

题目大意:

给你一颗\(n\)个节点的树,每个节点有\(A,B\)两个区域,每个区域可以为障碍物/冰块,只能在冰块上行走,每次行走你可以走到相邻节点的同个区域,或当前节点的另一个区域(前提是这个区域可以走),现在有\(m\)个操作和询问,操作是修改一个节点的两个区域,询问是问你从\(u\)往\(v\)走的路径上,最多经过多少块冰块(不一定要到达\(v\))。

数据范围:

测试点\(1\)~\(6\):\(n≤1000,m≤10000\)

测试点\(7\)~\(15\):\(n≤30000,m≤80000\)

测试点\(16\)~\(20\):\(n≤50000,m≤100000\)

读入格式:

第一行为\(n\)和\(m\)。

第\(2\)行到第\(n\)行,每行包含两个正整数\(x\)和\(y\),表示一条边。

接下来\(n\)行,每行包含两个字符。房间的两个区域,第一个字符为\(A\)区域,第二个字符为\(B\)​区域。(“.”是薄冰块,“#”是障碍物)

最后的\(m\)行,每行一个操作:

\(C\) \(u\) \(s\):将房间\(u\)里的两个区域修改为\(s\)。

\(Q\) \(u\) \(v\):从\(u\),往\(v\)走的路径上,最多经过的冰块数。

样例输入:

5 3
1 2
2 3
2 4
1 5
.#
..
#.
.#
..
Q 5 3
C 1 ##
Q 4 5

样例输出:

6
3

二,solution:

首先,这题一眼就是树剖,但是,与一般的题目不同的是,一个节点被分为了两个房间,那么树剖后线段树怎么搞呢?

那么分类讨论就可以了嘛。

我们钦定一个房间编号为\(0\),另一个为\(1\)。用\(1\)表示冰块,\(0\)表示障碍物。

我们树剖后,对于线段树,我们维护这些信息:

\(lans[2]\),表示从这个区间的最左边的节点的\(0/1\)号房间往右走,最多经过的冰块个数(不一定要到达最右边节点,旦不能超过)。

\(rans[2]\),表示从这个区间的最右边的节点的\(0/1\)号房间往右走,最多经过的冰块个数(不一定要到达最左边节点,旦不能超过)。

\(dis[2][2]\)表示从这个区间的最左边的节点的\(0/1\)号房间走到这个区间的最右边的节点的\(0/1\)号房间的最长经过的冰块个数,不能到达的话就为\(-INF\)

首先,新建一个节点的时候好办,直接分类讨论即可:

void make(node &k,int x){//新建一个节点: 
    //赋初始值
	memset(k.lans,0,sizeof(k.lans));
	memset(k.rans,0,sizeof(k.rans));
	memset(k.dis,-0x3f,sizeof(k.dis));
	k.l=k.r=x;//表示这个区间的范围
	x=rev[x];
	if(a[x][0]==0&&a[x][1]==0) return;//左右房间都是障碍物
	else if(a[x][0]==0&&a[x][1]==1){//左房间:障碍物;右房间:冰块
		k.lans[1]=k.rans[1]=1;
		k.dis[1][1]=1;
	}
	else if(a[x][0]==1&&a[x][1]==1){//左右房间都是冰块
		k.lans[1]=k.rans[1]=k.lans[0]=k.rans[0]=2;
		k.dis[0][0]=k.dis[1][1]=1;
		k.dis[0][1]=k.dis[1][0]=2;
	}
	else{//左房间:冰块;右房间:障碍物
		k.lans[0]=k.rans[0]=1;
		k.dis[0][0]=1;
	}
}

合并两个区间要麻烦一点,对于\(lans,rans\)还是类似分类讨论,\(dis\)则利用\(floyd\)的思想(其实还是枚举):

node add(node L,node R){//合并L,R子树
	node ans;
    //赋初始值
	memset(ans.lans,0,sizeof(ans.lans));
	memset(ans.rans,0,sizeof(ans.rans));
	memset(ans.dis,-0x3f,sizeof(ans.dis));
	ans.l=L.l,ans.r=R.r;
	for(int i=0;i<=1;i++){
		for(int j=0;j<=1;j++){
			ans.lans[i]=max(ans.lans[i],max(L.lans[i],L.dis[i][j]+R.lans[j]));
			ans.rans[i]=max(ans.rans[i],max(R.rans[i],R.dis[j][i]+L.rans[j]));
			for(int k=0;k<=1;k++) ans.dis[i][j]=max(ans.dis[i][j],L.dis[i][k]+R.dis[k][j]);
		}
	}
	return ans;
}

对于修改操作,我们每次直接线段树单点修改,和建树时一样,调用一下\(make\)函数即可。

但是,对于查询操作,就比较恶心的。

首先,这道题是规定了方向的,是从\(u\)往\(v\)走,路径应该这样:

我们查询的时候分类讨论,设\(ans1,ans2\)分别表示从\(u,v\)往\(lca(u,v)\)走的路径上的答案,如下图:

所以,我们需要把\(ans1\)的一些东西翻转一下(\(dis[1][1],dis[0][0]\)没有变化,不用翻转):

swap(ans1.lans[0],ans1.rans[0]);
swap(ans1.lans[1],ans1.rans[1]);
swap(ans1.dis[1][0],ans1.dis[0][1]);

然后把\(ans1,ans2\)合并到\(ans1\),答案就是\(\max\{ans1.lans[0],ans1.lans[1]\}\)

查询的代码:

int solve_2(){//路径查询
	int x,y,fx,fy;
	scanf("%d%d",&x,&y);
	node ans1,ans2;
	clean(ans1);clean(ans2);
	fx=top[x],fy=top[y];
	while(fx!=fy){
		if(deep[fx]>deep[fy]){
			ans1=add(ask(1,id[fx],id[x]),ans1);
			x=fa[fx];fx=top[x];
		}
		else{
			ans2=add(ask(1,id[fy],id[y]),ans2);
			y=fa[fy];fy=top[y];
		}
	}
	if(deep[x]>deep[y]){
		ans1=add(ask(1,id[y],id[x]),ans1);
	}
	else{
		ans2=add(ask(1,id[x],id[y]),ans2);
	}
	swap(ans1.lans[0],ans1.rans[0]);
	swap(ans1.lans[1],ans1.rans[1]);
	swap(ans1.dis[1][0],ans1.dis[0][1]);
	ans1=add(ans1,ans2);
	return max(ans1.lans[0],ans1.lans[1]);
}

代码:

#include<bits/stdc++.h>
using namespace std;

const int N=50005,INF=0x3f3f3f3f;
int n,m;
//树剖:
vector<int> G[N];
int son[N],siz[N],fa[N],deep[N];
int id[N],rev[N],top[N],dfn;
void dfs1(int x,int dad){
	siz[x]=1,fa[x]=dad,deep[x]=deep[dad]+1;
	for(auto y : G[x]){
		if(y==dad) continue;
		dfs1(y,x);
		if(siz[son[x]]<siz[y]) son[x]=y;
		siz[x]+=siz[y];
	}
}
void dfs2(int x){
	id[x]=++dfn;rev[dfn]=x;
	if(son[x]){
		top[son[x]]=top[x];
		dfs2(son[x]);
	}
	for(auto y : G[x]){
		if(id[y]) continue;
		top[y]=y;
		dfs2(y);
	}
}

int a[N][2];
struct node{
	int l,r;
	int lans[2],rans[2],dis[2][2];
}tree[N*4];
void clean(node &x){
	memset(x.lans,0,sizeof(x.lans));
	memset(x.rans,0,sizeof(x.rans));
	memset(x.dis,0,sizeof(x.dis));
}
node add(node L,node R){//合并L,R子树
	node ans;
	memset(ans.lans,0,sizeof(ans.lans));
	memset(ans.rans,0,sizeof(ans.rans));
	memset(ans.dis,-0x3f,sizeof(ans.dis));
	ans.l=L.l,ans.r=R.r;
	for(int i=0;i<=1;i++){
		for(int j=0;j<=1;j++){
			ans.lans[i]=max(ans.lans[i],max(L.lans[i],L.dis[i][j]+R.lans[j]));
			ans.rans[i]=max(ans.rans[i],max(R.rans[i],R.dis[j][i]+L.rans[j]));
			for(int k=0;k<=1;k++) ans.dis[i][j]=max(ans.dis[i][j],L.dis[i][k]+R.dis[k][j]);
		}
	}
	return ans;
}
void make(node &k,int x){//新建一个节点: 
	memset(k.lans,0,sizeof(k.lans));
	memset(k.rans,0,sizeof(k.rans));
	memset(k.dis,-0x3f,sizeof(k.dis));
	k.l=k.r=x;
	x=rev[x];
	if(a[x][0]==0&&a[x][1]==0) return;
	else if(a[x][0]==0&&a[x][1]==1){
		k.lans[1]=k.rans[1]=1;
		k.dis[1][1]=1;
	}
	else if(a[x][0]==1&&a[x][1]==1){
		k.lans[1]=k.rans[1]=k.lans[0]=k.rans[0]=2;
		k.dis[0][0]=k.dis[1][1]=1;
		k.dis[0][1]=k.dis[1][0]=2;
	}
	else{
		k.lans[0]=k.rans[0]=1;
		k.dis[0][0]=1;
	}
}
//线段树模板:
void build(int k,int l,int r){
	if(l==r){
		make(tree[k],l);
		tree[k].l=l,tree[k].r=r;
		return;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);build(k<<1|1,mid+1,r);
	tree[k]=add(tree[k<<1],tree[k<<1|1]);
}
void change(int k,int x){
	if(tree[k].l==tree[k].r&&tree[k].l==x){
		make(tree[k],x);
		return;
	}
	if(x<=tree[k<<1].r) change(k<<1,x);
	else change(k<<1|1,x);
	tree[k]=add(tree[k<<1],tree[k<<1|1]);
}
node ask(int k,int x,int y){
	if(x<=tree[k].l&&tree[k].r<=y) return tree[k];
	if(y<=tree[k<<1].r) return ask(k<<1,x,y);
	if(x>=tree[k<<1|1].l) return ask(k<<1|1,x,y);
	return add(ask(k<<1,x,y),ask(k<<1|1,x,y));
} 
void solve_1(){//单点修改
	int x;char ch1,ch2;
	cin>>x>>ch1>>ch2;
	a[x][0]=(ch1=='.');
	a[x][1]=(ch2=='.');
	change(1,id[x]);
}
int solve_2(){//路径查询
	int x,y,fx,fy;
	scanf("%d%d",&x,&y);
	node ans1,ans2;
    //ans1表示从x<-lca(x,y),ans2表示y<-lca(x,y),一开始他们都为0
	clean(ans1);clean(ans2);
	fx=top[x],fy=top[y];
    //由于有方向上的区别,需要分类讨论:
	while(fx!=fy){
		if(deep[fx]>deep[fy]){
			ans1=add(ask(1,id[fx],id[x]),ans1);
			x=fa[fx];fx=top[x];
		}
		else{
			ans2=add(ask(1,id[fy],id[y]),ans2);
			y=fa[fy];fy=top[y];
		}
	}
	if(deep[x]>deep[y]){
		ans1=add(ask(1,id[y],id[x]),ans1);
	}
	else{
		ans2=add(ask(1,id[x],id[y]),ans2);
	}
    //交换,使得ans1表示x->lca(x,y)
	swap(ans1.lans[0],ans1.rans[0]);
	swap(ans1.lans[1],ans1.rans[1]);
	swap(ans1.dis[1][0],ans1.dis[0][1]);
	ans1=add(ans1,ans2);//合并
	return max(ans1.lans[0],ans1.lans[1]);
}
int main(){
	scanf("%d%d",&n,&m);
	int x,y;
	for(int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		G[x].push_back(y);
		G[y].push_back(x);
	}
    //树剖:
	dfs1(1,0);
	top[1]=1;
	dfs2(1);
	char ch1,ch2;
	for(int i=1;i<=n;i++){
		cin>>ch1>>ch2; 
		a[i][0]=(ch1=='.');
		a[i][1]=(ch2=='.');
	}
    //建立线段树:
	build(1,1,n);
	char opt;
	while(m--){
		cin>>opt;
		if(opt=='C') solve_1();
		else printf("%d\n",solve_2()); 
	}
	return 0;
}

标签:int,ans1,id,ZJOI2011,TJ,rans,lans,道馆,dis
From: https://www.cnblogs.com/123456xwd/p/18050486

相关文章

  • Nestjs系列 Nestjs基础(二)
    providers使用该内容可以结合Nestjs中文网-自定义提供者查看创建一个nest项目,创建一个Personcrud模块。providers写法providers完整和简写@Injectable()装饰器将PersonService类标记为提供者。然后在Module中声明,即和PersonService做关联,个人感觉provider......
  • 洛谷题单指南-二分查找与二分答案-P3853 [TJOI2007] 路标设置
    原题链接:https://www.luogu.com.cn/problem/P3853题意解读:相邻路标的最大距离即空旷指数,空旷指数越小,用的路标越多,因此可以根据空旷指数将使用路标情况分成两类:路标数<=K,路标数>K,对空旷指数进行二分即可。解题思路:二分的判定条件为,给定空旷指数,计算需要的路标数只需遍历每两......
  • Nestjs系列(一) Nestjs基础
    快速使用NestjsNest项目的文件层级和JAVA项目的层级架构较为相似。Nest项目的层级架构统一由ControllerModuleService三个模块组成。安装nestcli,创建项目npminstall-g@nestjs/clinestnew[项目名]项目默认运行至http://localhost:3000/上当nest版......
  • P2065 [TJOI2011] 卡片 题解
    看大家建图时中间都连了质数点,发一个不用质数点的解法。我们可以先从源点向每一个蓝色卡片对应的点连一条边,再从每一个红色卡片对应的点向汇点连一条边。如果两张卡片可以一起拿走,那就在它们之间连一条边(蓝色连到红色),这些边的最大流量都是\(1\)。建好图以后我们就可以直接用Di......
  • Android里使用AspectJ实现双击自定义注解
    创建注解首先创建一个双击注解。importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;​/***<pre>*desc:双击*author:刘金*......
  • USACO24Bronze 游记兼 TJ All in Once
    我没有其他组别的号了。所以只能写Bronze的游记了。如果行的话,下一次我会写Silver的。一开始看了看三道题,T1T2感觉都很不可做,直奔T3。一看T3(Bessie很nb,会各种各样的东西,会科学,会魔法,今天我们发现她会分身术),不就是个二分吗?秒杀。好的,现在搞T1T2,直接《男左女右我......
  • FastJSON学习
    第一节:JSON数据格式回顾 JSON的数组格式:运行:结果:  JSON的对象格式:运行: 结果: ......
  • TJ -「HNOI2015」开店
    TJ-「HNOI2015」开店(洛谷)一,题意给你一棵\(n\)个节点的树,点有点权,边有边权,\(Q\)次询问每次让你求解点权在\(L\)到\(R\)之间的点到点\(u\)的距离和。(强制在线)数据范围:\(n\le1.5\times10^5,Q\le2\times10^5\)二,思路首先,我们先看弱化版,假设没有年龄的限制,看单个询问,设\(dis_i......
  • 第17天:信息打点-语言框架&开发组件&FastJson&Shiro&Log4j&SpringBoot等
    框架:简单代码的一个整合库,如果使用框架就只需要学习使用框架调用即可如:文件上传功能是需要很多代码来实现的,框架把这个代码进行封封装,调用即可影响:如果采用框架开发,代码的安全性是取决于框架的过滤机制 #Python-开发框架-Django&FlaskDjango1、识别插件2、Set-Cookie:expi......
  • ztjdtj.py
    代码片#-*-coding:utf-8-*-"""计算涨停价和跌停价,给定品种和昨天收盘价.Parameters----------lc:前收盘价,浮点数stype:整型,optional证券品种(0=可转债,1=股票).Thedefaultis0.Returns-------None.使用举例:%runztjdtj.py100#......