[2019 CSP-S Day1 T3]树上的数

122 篇文章 0 订阅

题目

传送门 to luogu

传送门 to nowcoder

思路

假设数字 1 1 1可以移动到 1 1 1号点(即:存在合法的移动方式),那么我们就要 不惜一切代价 1 1 1移动过去再说。

这告诉我们,难点是判断是否存在合法的方案——不与已有的移动要求冲突。

对于每个点,其邻边的删除顺序是独立的。发现一次移动等价于:

  • 对于起点,路径上的边必须第一个被删掉;
  • 对于中转点,路径上的入边与出边相继被删掉;
  • 对于终点,路径上的边必须最后被删掉。

然后用链表暴力判断即可。唯一不容易想到的一点,就是 过早封闭—— a a a必须第 x x x个被删掉, b b b必须倒数第 y y y个被删掉,如果 x + y ≠ d x x+y\ne d_x x+y=dx d x d_x dx表示 x x x的度),那么也不能连接。

其他情况自己推一下就好了。也可以看代码。

代码

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
inline int readint(){
	int a = 0, f = 1; char c = getchar();
	for(; c<'0' or c>'9'; c=getchar())
		if(c == '-') f = -1;
	for(; '0'<=c and c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

struct Edge{
	int to, nxt;
	Edge(){ }
	Edge(int T,int N):to(T),nxt(N){ }
};

const int MaxN = 2000;
int n, head[MaxN+1], cntEdge;
Edge edge[MaxN<<1];
int located[MaxN+1], degree[MaxN+1];

void addEdge(int from,int to){
	edge[cntEdge] = Edge(to,head[from]);
	head[from] = cntEdge ++;
	++ degree[from];
}
void input(){
	n = readint(); cntEdge = 0;
	for(int i=1; i<=n; ++i)
		head[i] = -1, degree[i] = 0;
	for(int i=1; i<=n; ++i)
		located[i] = readint();
	for(int i=1,x,y; i<n; ++i){
		x = readint(), y = readint();
		addEdge(x,y), addEdge(y,x);
	}
}

struct Node{
	Node *pre, *suf;
	int preLen, sufLen, id;
	void dispose(){
		pre = suf = nullptr; // C++11对于(void*)0的新写法 
		preLen = sufLen = (MaxN<<2);
	}
}node[MaxN<<1], *Head[MaxN+1], *tail[MaxN+1];

bool beTail(Node *o,int pos){ // 能否成为最后一条 
	if(tail[pos] != nullptr) return false;
	if(o->suf != nullptr) return false;
	if(o->preLen < degree[pos])
		return false;
	return true;
}
bool beHead(Node *o,int pos){ // 能否成为第一条 
	if(Head[pos] != nullptr) return false;
	if(o->pre != nullptr) return false;
	if(o->sufLen < degree[pos])
		return false;
	return true;
}
bool linkNode(Node *a,Node *b,int pos){
	if(a->suf == b) return true;
	if(a->suf != nullptr or b->pre != nullptr)
		return false;
	if(tail[pos] == a or Head[pos] == b)
		return false;
	if(a->preLen+b->sufLen < degree[pos])
		return false; // 过早封闭 
	if(a->id == b->id)
		return false; // 已经在一条链中了 
	return true;
}
void linkNode(Node *a,Node *b){
	a->suf = b, b->pre = a;
	for(Node *o=b; o!=nullptr; o=o->suf){
		o->preLen = o->pre->preLen+1;
		o->id = o->pre->id;
	}
	for(Node *o=a; o!=nullptr; o=o->pre)
		o->sufLen = o->suf->sufLen+1;
}
void toBeHead(Node *o,int pos){
	Head[pos] = o; o->preLen = 1;
	for(o=o->suf; o!=nullptr; o=o->suf)
		o->preLen = o->pre->preLen+1;
}
void toBeTail(Node *o,int pos){
	tail[pos] = o; o->sufLen = 1;
	for(o=o->pre; o!=nullptr; o=o->pre)
		o->sufLen = o->suf->sufLen+1;
}

int answer;
void dfs(int x,int pre){
	pre ^= 1;
	if(beTail(&node[pre],x))
		answer = min(answer,x);
	for(int i=head[x]; ~i; i=edge[i].nxt){
		if(i == pre) continue;
		if(not linkNode(&node[pre],&node[i],x))
			continue;
		dfs(edge[i].to,i);
	}
}
bool update(int x,int pre){
	pre ^= 1;
	if(x == answer){
		toBeTail(&node[pre],x);
		return true;
	}
	for(int i=head[x]; ~i; i=edge[i].nxt)
		if(i != pre and update(edge[i].to,i)){
			linkNode(&node[pre],&node[i]);
			return true;
		}
	return false;
}
void solve(){
	for(int i=0; i<cntEdge; ++i){
		node[i].dispose();
		node[i].id = i;
	}
	for(int i=1; i<=n; ++i)
		Head[i] = tail[i] = nullptr;
	for(int i=1,x; i<=n; ++i){
		x = located[i], answer = n+1;
		for(int j=head[x]; ~j; j=edge[j].nxt)
			if(beHead(&node[j],x))
				dfs(edge[j].to,j);
		for(int j=head[x]; ~j; j=edge[j].nxt)
			if(update(edge[j].to,j)){
				toBeHead(&node[j],x);
				break;
			}
		printf("%d ",answer);
	}
	putchar('\n');
}

int main(){
	for(int T=readint(); T; --T){
		input();
		solve();
	}
	return 0;
}

后记

考场时思考过“边删除的先后关系”作为判断依据。但是我是 全局性的考虑,就会导致很多问题,无法实现。

——所以 忽略无用信息(或者说 分类讨论?)很重要!我们要充分利用 每个点的邻边的删除顺序是独立的 这一 NB \text{NB} NB特征。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值