Codeforces 707D

本文介绍了一种解决动态书架操作问题的离线算法,通过DFS和懒标记优化,实现在n*m书架上高效进行放置、拿取和行反转操作,并能快速回溯至任意操作版本。关键在于理解操作序列的抽象和DFS的回溯策略,以及如何利用边的存储结构(链式前向星)节省空间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给一个n行m列的书架,m次操作

1 x y 在x行y列的位置上放置一本书,如果有书在这个位置,跳过

2 x y 在x行y列的位置上拿去一本书,如果没有书在这个位置,跳过

3.x 反转x行的所有位置,即在x行所有没有书的地方放一本书,有书的地方拿走

4.x 回到第x次操作后

前三种操作是容易的,但值得注意的是,第三种操作是需要用懒标记来标记是否有反转过的,两次反转会抵消,所以某个位置的真实情况应该是has[i][j]^tag[i][j],不用懒标记而是暴力模拟时间复杂度会高。

难点在于4,4是回到之前的某个版本,可以把每次操作抽象成一个点,编号为u点就代表刚刚操作完u,那么如果要回到x操作之后,我们只需要把i操作接到x之后,也就是i与x连边,如果是1-3操作,那么只需与上个操作连边,然后借助dfs回溯的特性,我们如果需要回到x版本,那么在i位置是没有路可走的,会一路回溯到x,此时就可以进行回溯之后的操作了。关于如何回溯,一开始我想的是保存整个局面,但这样空间复杂度是不可行的,我们只需要在回溯之前消除这次操作的影响就可以,因为我们是每个局面分别计算,而局面1+操作=局面2,局面2-操作=局面1,这个思想应该是基础dfs就有了,属于是走弯路了。

需要注意的是,这个算法需要保存所有操作,离线处理,在保存答案的时候,应该是按照点的编号保存,而不是时间顺序,因为在回溯的路径上可能有经过某个点需要多个操作。

关于存边,链式前向星应该是可行的,补题的时候思考错误认为要按照时间顺序,所以改用了vector,但其实不需要关心时间的,关心的是局面状态之间的转移。

比如到达3后有操作要求回到4,但是我们是按照dfs序来离线操作的,所以获取答案的顺序不一定是时间顺序。

 补题的时候设置了个计数器done,按照时间顺序来编号答案了,一直WA9,看了数据才反应过来。

另外,回溯的时候切记要删去这次操作的影响。

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

struct node
{
	int to,x,y,op;
};

vector<node>go[100005];
int n,m,q,done;
bool has[1005][1005],tag[1005];
int sum[1005],tot,ans[100005];

void add(int u,int v,int op,int x,int y)
{
	node add;
	add.to=v;add.op=op;
	add.x=x;add.y=y;
	go[u].push_back(add);
}

void dfs(int u,node todo)
{
	int op=todo.op,x=todo.x,y=todo.y;
	bool suc_to_add=false,suc_to_del=false;//在这次操作中,保存是否成功放置书或者成功拿走书的变量
	if(op==1)
	{
		if(!(has[x][y]^tag[x]))
		{
			sum[x]++;tot++;
			suc_to_add=true;
			has[x][y]=!has[x][y];
		}
	}
	else if(op==2)
	{
		if(has[x][y]^tag[x])
		{
			sum[x]--;tot--;
			suc_to_del=true;
			has[x][y]=!has[x][y];
		}
	}
	else if(op==3)
	{
		tag[x]=!tag[x];
		tot+=m-2*sum[x];
		sum[x]=m-sum[x];
	}
	ans[u]=tot;//此处保存答案要按照编号保存,不是时间顺序
	for(auto i:go[u])
	{
		int v=i.to;
		dfs(v,i);
	}
	if(op==1&&suc_to_add)
	{
		has[x][y]=!has[x][y];
		sum[x]--;tot--;
	}
	else if(op==2&&suc_to_del)
	{
		has[x][y]=!has[x][y];
		sum[x]++;tot++;
	}
	else if(op==3)
	{
		tag[x]=!tag[x];
		tot+=m-2*sum[x];
		sum[x]=m-sum[x];
	}
}

int main()
{
	ios::sync_with_stdio(false);
	cin>>n>>m>>q;
	for(int i=1;i<=q;i++)
	{
		int op,x,y;cin>>op;
		if(op==1||op==2)
		{
			cin>>x>>y;
			add(i-1,i,op,x,y);
		}
		else if(op==3)
		{
			cin>>x;
			add(i-1,i,op,x,y);
		}
		else
		{
			cin>>x;
			add(x,i,op,x,y);
		}
	}
	for(auto i:go[0])
	{
		int v=i.to;
		dfs(v,i);
	}
	for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
 	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值