P5659-[CSP-S2019]树上的数【贪心】

1|0正题

题目链接:https://www.luogu.com.cn/problem/P5659


1|1题目大意

给出n个点的一棵树,每个节点上有一个数字,你每次可以选择一条边删除然后交换连接的两个点的数字,在删完所有数字后设pi表示数字i所在节点编号,要求使得排列p的字典序。

1n2000,1T10


1|2解题思路

好阴间的题目

考虑对与一个点x的转移肯定是如下图类似的情况并且每个点连接的边的删除顺序是独立的(如果不独立证明出现了环,树上显然没有环)
在这里插入图片描述
考虑上图我们发现产生的数字搬运是x1,12,23,3x,而边的删除顺序是(x,3)(x,2)(x,1)(红色箭头画反了)。不难发现的是因为必须删除所有边,所以最后肯定是x和它周围的节点连接形成一个搬运环。

然后因为字典序最小,所以考虑贪心,如果快速对于(s,t)判断s上的数字能否搬运到t上。

先考虑在st路径上(不包括s,t)的节点x需要满足的条件,记上一个节点为fa,下一个节点为y

  • 如果有数字走yx或者xfa搬运过那么不行
  • 如果边xy或者fax正反都搬运过数字那么不行
  • 如果此时对于节点x的边中加上fay这条边后形成了一个环且不是所有x及其周围的数字都在环上的话那么显然不行。
  • 如果边(x,fa)已经要求在(x,y)之后再删除了那么显然不合法(也就是红色的箭头形成了环)

对于根节点和终止节点则类似只是减少了上面第4个要求因为显然这条边必须是最早/最晚删除的。

然后用类似链表的结构来维护每个节点连出去边的情况来判断后两个条件,前两个条件则很好判断。并且如果保证了对于每个节点x都满足它上面的数字不会搬回自己所在处就可以保证所有边必须删除了。

然后每次从未确定的最小的数字所在节点出发搜索所有合法的终止节点然后拿最小值再沿路更新即可。

时间复杂度:O(Tn2)


1|3code

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

__EOF__

本文作者QuantAsk
本文链接https://www.cnblogs.com/QuantAsk/p/15399158.html
关于博主:退役OIer,GD划水选手
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   QuantAsk  阅读(123)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示