P9697 [GDCPC 2023] Canvas 题解

P9697 [GDCPC 2023] Canvas

思路

考虑后面的操作会覆盖掉前面的操作,因此我们考虑倒序来做。这样被操作过的点就一定不会再改变了。
注意接下来的叙述都是倒过来了的,“前” 变成了后,“后” 变成了前。

考虑形如 \((x,2,y,2)\) 的操作一定最先做,\((x,1,y,1)\) 的操作一定最后做。因此只需要判断形如 \((x,1,y,2)\) 的操作的顺序。
发现如果有两个操作形如 \((x,1,y,2),(y,1,z,2)\),那么前一个操作一定先做。(再次注意是倒序考虑,被赋值过的点一定不会变了)

那这个就形成了一个类似于有向边的关系。
我们将序列上的位置看做点,赋值的操作看成边,那么走边就代表操作。如操作 \((x,1,y,2)\) 就会使 \(x\)\(y\) 连边。
由于图上只有形如 \((x,1,y,2)\) 的操作,因此入度为 0 的点的最终答案会是 1,其他所有有入度的点的答案都会是 2。

因此我们考虑将强联通分量给缩起来,从入度为 0 的点开始 dfs 即可。每走过一条边就将这条边所对应的操作压入栈中。
但是由于还有形如 \((x,2,y,2)\) 的操作,因此我们优先从被这种操作标记过的点且入度为 0 的点开始 dfs。

最后统计答案是简单的模拟即可。

code

注意建出来的图不一定连通。
在代码实现上,我们并不需要真正将缩点后的图建出来。具体而言,扫两遍序列。第一次只从被标记一定为 2 且入度为 0 的点开始 dfs。第二次从任意入度为 0 的点开始 dfs。

而统计答案并不需要在 dfs 的时候统计。由于我们已经得到了操作的顺序,因此我们可以直接暴力模拟操作的过程统计答案。

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+7;
#define pii pair <int,int>
#define mp make_pair
int n,m,ans=0,sign[N],tot[N],top,stk[N],dfn[N],low[N],vis[N],dfncnt=0,blockcnt=0,bel[N],du[N],t[N],cnt=0,a[N];
vector <pii> q[N];vector <int> scc[N];
struct node{int u,x,v,y;}que[N];
void init(){
	for(int i=1;i<=n;i++) sign[i]=tot[i]=vis[i]=dfn[i]=bel[i]=a[i]=0,q[i].clear();
	for(int i=1;i<=blockcnt;i++)scc[i].clear(),du[i]=0;
	top=dfncnt=blockcnt=cnt=0;
}
void tar(int u){
	dfn[u]=low[u]=++dfncnt;vis[u]=1;stk[++top]=u;
	for(int i=0;i<tot[u];i++){
		int v=q[u][i].first; //注意由于是无向图不应该考虑父亲 
		if(!dfn[v]){tar(v);low[u]=min(low[v],low[u]);}
		else if(vis[v])low[u]=min(dfn[v],low[u]);
	}
	if(dfn[u]==low[u]){
		++blockcnt;
		do{
			scc[blockcnt].push_back(stk[top]);vis[stk[top]]=0;
			bel[stk[top]]=blockcnt;
		}
		while(stk[top--]!=u);
	}
}
void dfs(int u){
	vis[u]=1;
	for(int i=0;i<tot[u];i++){
		int v=q[u][i].first,id=q[u][i].second;
		t[++cnt]=id;if(!vis[v])dfs(v);
	}
}
void solve(){
	cin>>n>>m;
	for(int i=1,u,v,x,y;i<=m;i++){
		cin>>u>>x>>v>>y;if(x>y)swap(u,v),swap(x,y);que[i]={u,x,v,y};
		if(x==1&&y==2) q[u].push_back(mp(v,i)),tot[u]++;
		if(x==2) sign[u]=sign[v]=1,t[++cnt]=i;
	}
	for(int i=1;i<=n;i++)if(!dfn[i])tar(i);
	for(int u=1;u<=n;u++){for(int i=0;i<tot[u];i++){int v=q[u][i].first;if(bel[v]!=bel[u])du[bel[v]]++;}}
	for(int i=1;i<=n;i++){if((!du[bel[i]])&&sign[i]&&(!vis[i]))dfs(i);}           //优先考虑有被形如 (x,2,y,2) 操作的点的强联通分量
	for(int i=1;i<=n;i++){if((!vis[i])&&(!du[bel[i]]))dfs(i);}
	for(int i=1;i<=m;i++) if(que[i].x+que[i].y==2)t[++cnt]=i;
	for(int i=cnt;i>=1;i--) a[que[t[i]].u]=que[t[i]].x,a[que[t[i]].v]=que[t[i]].y;//模拟操作
	int ans=0;
	for(int i=1;i<=n;i++) ans+=a[i];
	cout<<ans<<'\n';
	for(int i=cnt;i>=1;i--){cout<<t[i]<<' ';}cout<<'\n';
}
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int T;cin>>T;while(T--) init(),solve();
	return 0;
} 
posted @ 2025-05-29 14:33  all_for_god  阅读(30)  评论(0)    收藏  举报