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;
}

浙公网安备 33010602011771号