2020年上学期第二场——FJUT&FJNU友谊赛 J题 萌萌哒的偏序关系

### 题目链接 ###

题目大意:

\(n\) 个三重集合 { \(pos\) \(,\) \(a\) \(,\) \(b\) } , \(pos\) 是唯一的,它表示集合编号 ( \(1\) \(≤\) \(pos\) \(≤\) \(n\) )。

\(a\)\(pos\)\(b\)\(pos\) 分别表示第 \(pos\) 号集合中的元素 \(a\)\(b\)

在这些集合中,给定 \(n-1\) 个偏序对 ( \(i\) \(,\) \(j\) ) ( 对于所有偏序对中,右项中 \(j\) 的值只会出现一次 )
表示一组大小关系 \(a\)\(i\) \(>\) \(a\)\(j\) ( \(i\) \(≠\) \(j\) \(∈\) \(pos\) ),且对于每个集合中的 \(b\) 都会有一个初始值。

定义 \(O\) ( \(i\) \(,\) \(j\) \(,\) \(s\) ) 表示\(:\)
在给定的 \(n-1\) 个大小关系下,集合编号 \(t\) 满足 ( \(a\)\(t\) \(>\) \(a\)\(i\) \(,\) \(a\)\(t\) \(>\)\(a\)\(j\) ),将所有满足的集合 \(t\) 中的 元素 \(b\)\(t\) 都加上 \(s\)
在执行 \(k\)  个 \(O\) ( \(i\) \(,\) \(j\) \(,\) \(s\) )   之后,求出所有集合中的 \(b\) 之和。

保证给定的关系合法,不会出现 \(a\)\(i\) \(>\) \(a\)\(j\)\(a\)\(i\) \(<\) \(a\)\(j\)

分析:

1、通过偏序关系,可以想到拓扑排序出一个大小关系的全序。

2、对于两个集合 \(i\)\(j\) ,若 \(a\)\(i\) \(>\) \(a\)\(j\) ,考虑连出一条有向边 \(i\) \(→\) \(j\) ,由于不会出现 \(i\) \(→\) \(j\)\(i\) \(←\) \(j\) 的情况,故将这 \(n-1\) 个偏序都这样处理,那么它们连接起来一定不会有环,且必是一个能被拓扑排序的有向图。

3、由于偏序对中右项的 \(j\) 只会出现一次,那么相当于这样的有向图中的每一个点的入度至多为 1 ,由于只有 \(n-1\) 条有向边,这样连起来的图形必然是一棵完整的树。

4、开始考虑 \(O\) ( \(i\) \(,\) \(j\) \(,\) \(s\) ),对于集合编号 \(t\) 满足 ( \(a\)\(t\) \(>\) \(a\)\(i\) \(,\) \(a\)\(t\) \(>\)\(a\)\(j\) ) ,那么 \(t\) 必是 \(i\)\(j\) 的共同父亲或祖先。这样就相当于求得 \(LCA\) \((\) \(i\) \(,\) \(j\) \()\) ,然后 \(lca\)\(lca\) 的父亲和祖先节点的 \(b\) 值 加上 \(s\) 即可,这里差分标记它或者记录 \(lca\) 的深度就可以\(O(1)\) 处理。

5、而对于 \(LCA\) \((\) \(i\) \(,\) \(j\) \()\) 等于 \(i\) 或者 \(j\) 时要特殊考虑,因为 \(lca\) 上的 \(a\) 关系并不满足这两个关系 \(a\)\(lca\) \(>\) \(a\)\(i\) \(,\) \(a\)\(lca\) \(>\)\(a\)\(j\),对于这种情况,应该跳过 \(lca\) ,再向 \(lca\) 上一个点处理即可。



比如 \(4\)\(2\)\(lca\)\(2\) ,只有 \(1\) 才满足 \(a\)\(1\) \(>\) \(a\)\(2\)\(a\)\(1\) \(>\)\(a\)\(4\) 的关系,而 作为 \(lca\)\(2\) 并不满足,故需要跳过它。


树上差分:
\(lca\)\(lca\) 上一个点 的差分值 += \(s\) 即可,要注意的是,\(pre[]\) (tarjan 求 lca 中的并查集数组)并不是一直指向父亲节点的(还没有往上并),所以要开一个数组记录父亲节点。

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
typedef long long ll;
const double PI=acos(-1.0);
const ll mod = 1e9 + 7;
const int maxn = 100008;
const int inf = 0x3f3f3f3f;
const ll INF = 1e18 + 10;
const double eps = 1e-6;
using namespace std;
int n,k,cnt,tot,root;
int head[maxn],qhead[maxn],in[maxn],pre[maxn],fa[maxn];
ll ans,b[maxn],f[maxn];
bool vis[maxn],flag[maxn<<1];
struct Query{
	int to;
	ll val;
	int next;
}q[maxn<<1];
struct Edge{
	int to;
	int next;
}edge[maxn];
int find(int x){
	if(pre[x]==x) return x;
	return pre[x]=find(pre[x]);
}
inline void add(int u,int v){
	edge[++cnt].to=v;
	edge[cnt].next=head[u];
	head[u]=cnt;
	return;
}
inline void qadd(int u,int v,ll w){
	q[++tot].to=v;
	q[tot].val=w;
	q[tot].next=qhead[u];
	qhead[u]=tot;
	return;
}
void tarjan(int u){
	vis[u]=true;
	for(int i=head[u];~i;i=edge[i].next){
		int v=edge[i].to;
		fa[v]=u;
		tarjan(v);
		pre[v]=u;
	}
	for(int i=qhead[u];~i;i=q[i].next){
		int v=q[i].to;
		if(vis[v]&&!flag[i]){
			flag[i]=flag[i^1]=true;
			int lca=find(v);
			if(lca!=u&&lca!=v) f[lca]+=q[i].val;
			else f[fa[lca]]+=q[i].val;
		}
	}
	return;
}
void dfs(int u){
	for(int i=head[u];~i;i=edge[i].next){
		int v=edge[i].to;
		dfs(v);
		f[u]+=f[v];
	}
	ans+=f[u];
	return;
}
int main()
{
	tot=cnt=-1;
	memset(head,-1,sizeof(head));
	memset(qhead,-1,sizeof(qhead));
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++) {
		scanf("%lld",&b[i]);
		ans+=b[i];
		pre[i]=i;
	}
	int A,B;
	ll C;
	for(int i=1;i<=n-1;i++){
		scanf("%d%d",&A,&B);
		add(A,B),in[B]++;
	}
	for(int i=1;i<=n;i++) {
		if(!in[i]) {
			root=i;
			break;
		}
	}
	for(int i=1;i<=k;i++){
		scanf("%d%d%lld",&A,&B,&C);
		qadd(A,B,C),qadd(B,A,C);
	}
	tarjan(root);
	dfs(root);
	printf("%lld\n", ans);
}

或者直接标记各个点的深度,即可以直接保存该点的父亲或祖先节点的个数来统计答案了。

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
typedef long long ll;
const double PI=acos(-1.0);
const ll mod = 1e9 + 7;
const int maxn = 100008;
const int inf = 0x3f3f3f3f;
const ll INF = 1e18 + 10;
const double eps = 1e-6;
using namespace std;
int n,k,cnt,tot,root;
int head[maxn],qhead[maxn],in[maxn],pre[maxn],fa[maxn],deep[maxn];
ll ans,b[maxn],f[maxn];
bool vis[maxn],flag[maxn<<1];
struct Query{
	int to;
	ll val;
	int next;
}q[maxn<<1];
struct Edge{
	int to;
	int next;
}edge[maxn];
int find(int x){
	if(pre[x]==x) return x;
	return pre[x]=find(pre[x]);
}
inline void add(int u,int v){
	edge[++cnt].to=v;
	edge[cnt].next=head[u];
	head[u]=cnt;
	return;
}
inline void qadd(int u,int v,ll w){
	q[++tot].to=v;
	q[tot].val=w;
	q[tot].next=qhead[u];
	qhead[u]=tot;
	return;
}
void tarjan(int u){
	vis[u]=true;
	for(int i=head[u];~i;i=edge[i].next){
		int v=edge[i].to;
		fa[v]=u;
		deep[v]=deep[u]+1;
		tarjan(v);
		pre[v]=u;
	}
	for(int i=qhead[u];~i;i=q[i].next){
		int v=q[i].to;
		if(vis[v]&&!flag[i]){
			flag[i]=flag[i^1]=true;
			int lca=find(v);
			if(lca!=u&&lca!=v) ans+=deep[lca]*q[i].val;
			else ans+=deep[fa[lca]]*q[i].val;
		}
	}
	return;
}
int main()
{
	tot=cnt=-1;
	memset(head,-1,sizeof(head));
	memset(qhead,-1,sizeof(qhead));
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++) {
		scanf("%lld",&b[i]);
		ans+=b[i];
		pre[i]=i;
	}
	int A,B;
	ll C;
	for(int i=1;i<=n-1;i++){
		scanf("%d%d",&A,&B);
		add(A,B),in[B]++;
	}
	for(int i=1;i<=n;i++) {
		if(!in[i]) {
			root=i;
			break;
		}
	}
	for(int i=1;i<=k;i++){
		scanf("%d%d%lld",&A,&B,&C);
		qadd(A,B,C),qadd(B,A,C);
	}
	deep[root]=1;
	tarjan(root);
	printf("%lld\n", ans);
}
posted @ 2020-02-29 15:21  Sonrisa丶  阅读(195)  评论(0)    收藏  举报