P5659-[CSP-S2019]树上的数【贪心】
正题
题目链接:https://www.luogu.com.cn/problem/P5659
题目大意
给出\(n\)个点的一棵树,每个节点上有一个数字,你每次可以选择一条边删除然后交换连接的两个点的数字,在删完所有数字后设\(p_i\)表示数字\(i\)所在节点编号,要求使得排列\(p\)的字典序。
\(1\leq n\leq 2000,1\leq T\leq 10\)
解题思路
好阴间的题目
考虑对与一个点\(x\)的转移肯定是如下图类似的情况并且每个点连接的边的删除顺序是独立的(如果不独立证明出现了环,树上显然没有环)
考虑上图我们发现产生的数字搬运是\(x\rightarrow 1,1\rightarrow 2,2\rightarrow 3,3\rightarrow x\),而边的删除顺序是\((x,3)\rightarrow (x,2)\rightarrow (x,1)\)(红色箭头画反了)。不难发现的是因为必须删除所有边,所以最后肯定是\(x\)和它周围的节点连接形成一个搬运环。
然后因为字典序最小,所以考虑贪心,如果快速对于\((s,t)\)判断\(s\)上的数字能否搬运到\(t\)上。
先考虑在\(s\rightarrow t\)路径上(不包括\(s,t\))的节点\(x\)需要满足的条件,记上一个节点为\(fa\),下一个节点为\(y\)。
- 如果有数字走\(y\rightarrow x\)或者\(x\rightarrow fa\)搬运过那么不行
- 如果边\(x\rightarrow y\)或者\(fa\rightarrow x\)正反都搬运过数字那么不行
- 如果此时对于节点\(x\)的边中加上\(fa\rightarrow y\)这条边后形成了一个环且不是所有\(x\)及其周围的数字都在环上的话那么显然不行。
- 如果边\((x,fa)\)已经要求在\((x,y)\)之后再删除了那么显然不合法(也就是红色的箭头形成了环)
对于根节点和终止节点则类似只是减少了上面第\(4\)个要求因为显然这条边必须是最早/最晚删除的。
然后用类似链表的结构来维护每个节点连出去边的情况来判断后两个条件,前两个条件则很好判断。并且如果保证了对于每个节点\(x\)都满足它上面的数字不会搬回自己所在处就可以保证所有边必须删除了。
然后每次从未确定的最小的数字所在节点出发搜索所有合法的终止节点然后拿最小值再沿路更新即可。
时间复杂度:\(O(Tn^2)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2100;
struct node{
int to,next;
}a[N<<1];
int T,n,tot,ls[N],p[N],d[N],d1[N],d2[N],fa[N];
int from[N],got[N],e[N][N],las[N][N],nxt[N][N];
bool v[N];
void addl(int x,int y){
a[++tot].to=y;
a[tot].next=ls[x];
ls[x]=tot;d[y]++;
return;
}
void dfs(int x,int root){
for(int i=ls[x];i;i=a[i].next){
int y=a[i].to;
if(y==fa[x])continue;v[y]=1;
if(x!=root){
if(e[fa[x]][x]==fa[x]||e[x][y]==x)v[y]=0;//反向搬运过
if(e[fa[x]][x]==0||e[x][y]==0)v[y]=0;//被别的方向走过
if(nxt[x][y]==from[x]&&las[x][fa[x]]==got[x]&&d[x]*2+d1[x]+d2[x]-2>0)
v[y]=0;//构成一条偏序链
if(nxt[x][y]==fa[x])v[y]=0;//构成环
}
else{
if(e[x][y]==x)v[y]=0;//反向搬运过
if(e[x][y]==0)v[y]=0;//被别的方向走过
if(from[x]&&nxt[x][y]==from[x]&&d[x]+d1[x]+d2[x]-1>0)
v[y]=0;//构成一条偏序链
}
v[y]&=v[x];fa[y]=x;
dfs(y,root);
}
if(x==root)v[x]=0;
else{
if(from[x])v[x]=0;
if(got[x]&&nxt[x][got[x]]==fa[x]&&d[x]+d1[x]+d2[x]-1>0)v[x]=0;
}
return;
}
int main()
{
scanf("%d",&T);
while(T--){
scanf("%d",&n);tot=0;
memset(d,0,sizeof(d));
memset(ls,0,sizeof(ls));
memset(d1,0,sizeof(d1));
memset(d2,0,sizeof(d2));
memset(got,0,sizeof(got));
memset(from,0,sizeof(from));
for(int i=1;i<=n;i++)scanf("%d",&p[i]);
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
addl(x,y);addl(y,x);
e[x][y]=e[y][x]=-1;
las[x][y]=nxt[x][y]=y;
las[y][x]=nxt[y][x]=x;
}
for(int i=1;i<=n;i++){
int x=p[i];
for(int j=1;j<=n;j++)fa[j]=0;
v[x]=1;dfs(x,x);
int y=0;
for(int j=1;j<=n;j++)
if(v[j]){y=j;break;}
printf("%d ",y);
from[y]=fa[y];
while(fa[y]!=x){
if(e[fa[y]][y]==-1){
e[fa[y]][y]=e[y][fa[y]]=fa[y];
d[y]--;d2[y]++;
d[fa[y]]--;d1[fa[y]]++;
}
else{
e[fa[y]][y]=e[y][fa[y]]=0;
d1[y]--;d2[fa[y]]--;
}
int z=y;y=fa[y];
las[y][nxt[y][z]]=las[y][fa[y]];
nxt[y][las[y][fa[y]]]=nxt[y][z];
//数字fa->y->z,所以y->fa的连接y->z
}
if(e[fa[y]][y]==-1){
e[fa[y]][y]=e[y][fa[y]]=fa[y];
d[y]--;d2[y]++;
d[fa[y]]--;d1[fa[y]]++;
}
else{
e[fa[y]][y]=e[y][fa[y]]=0;
d1[y]--;d2[fa[y]]--;
}
got[x]=y;
}
putchar('\n');
}
return 0;
}