2023/6/4 模拟赛

T1农夫约的假期

好吧还是写一下。发现对于某一个点,向右移动一格的魔音值会变化他左侧波源的数量减去右侧波源的数量再乘上 \(z\),向下移动也类似(就是改成上面的和下面的波源)。而且对于同一行或同一列,每次移动的变化是一致的,也就是说,每一行的最小值在移动之后仍为移动后这一行的最小值。那么我们可以先暴力求出 \((1, 1)\) 点的魔音值,向右移动确定最小值纵坐标,再向下移动确定最小值横坐标即可。

T2小 小x游世界树

题目明示换根dp

首先要搞到没换根的时候答案是啥。我们发现一条边会对这条边连接的子树中的所有点造成一次贡献,即

\(f_u = \sum{(f_v+(w-d_u)*size_v)}\)

那么,我们现在就求出来了以每个节点为根的子树的答案。如何转移到以每个节点为根后整棵树的答案呢?

我们设 \(g\) 数组来表示最终答案。那么我们发现,对于一个父亲为 \(x\) 的子树 \(y\),它下方的答案不变可以直接加上,他父亲的答案中不包括子树 \(y\) 贡献的答案也可以直接加上,可以发现,这两部分的和就是在 \(g_x\) 的基础上减走了子树 \(y\) 通过边造成的贡献。同时,换了根以后,还多了个以 \(y\) 为根后子树 \(x\) 中所有点通过边造成的贡献。故:

\(g_y =g_x-(w-d_x)*size_y+(w-d_y)*(n-size_y)\)

结束。

Code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 700050;

inline ll read(){
	ll x = 0, f = 1; char ch = getchar();
	while(ch<'0' || ch>'9'){if(ch == '-') f = -1; ch = getchar();}
	while(ch>='0'&&ch<='9'){x = x*10+ch-48; ch = getchar();}
	return x*f;
}
int head[N], tot;
struct node{
 int nxt, to; ll  w;
}edge[N<<1];
void add(int u, int v, ll w){
	edge[++tot].nxt = head[u];
	edge[tot].to = v;
	edge[tot].w = w;
	head[u] = tot;
}
ll n, a[N];
ll siz[N];
ll f[N], g[N], ans = 1, mn;
void dfs1(ll u, ll fa){
	siz[u] = 1;
	for(ll i = head[u]; i; i = edge[i].nxt){
		ll v = edge[i].to;
		if(v == fa){
			continue;
		}
		dfs1(v, u);
		f[u] += (f[v] + (edge[i].w-a[u])*siz[v]);
		siz[u]+=siz[v];
	}
}
void dfs2(ll u, ll fa){
	if(g[u]<mn){
		mn = g[u];
		ans = u;
	}
	else if(g[u] == mn){
		ans = min(ans, u);
	}
	for(ll i = head[u]; i; i = edge[i].nxt){
		ll v = edge[i].to;
		if(v == fa){
			continue;
		}
		g[v] = g[u]-(edge[i].w-a[u])*siz[v]+(edge[i].w-a[v])*(n-siz[v]);
		dfs2(v, u);
	}
}
int main(){
	n = read();
	for(int i = 1; i<=n; i++){
		a[i] = read();
	}
	for(int i = 1; i<n; i++){
		ll u = read(), v = read(), w = read();
		add(u, v, w);
		add(v, u, w);
	}
	dfs1(1, 0);
	mn = g[1] = f[1];
	dfs2(1, 0);
	printf("%lld\n%lld\n", ans, mn);
	return 0;
}

T3观察

其实我更想说这个题。这个题很有意思。它给你一棵树,两种操作,一种是改变一个节点的颜色(只有黑色白色),另一种是询问这个节点和所有黑色节点的最深的lca是谁。

刚拿到这个题我其实是有点蒙的。不过既然和lca有关,那少不了树剖。然后我们可以思考一下性质。我们发现,如果一个节点对应的子树中有黑色棋子,那这个节点就是答案。因为它是它子树中所有节点的lca,而和其它非子树节点的lca只会比它浅。我们用线段树维护一下黑色棋子的数量就行了。至于查询,一颗子树中的dfn一定是连续的,所有可以直接查。

那如果没有呢?

我们又知道,一个节点与其他节点的lca可以通过跳链顶来找到。我们可以每次跳链顶。因为如果链顶中没有,那这条链上所有节点的子树中都不会有。同理,如果找到了,那么最深的lca一定是这条链上的某个节点。二分这条链即可。

复杂度 \(q*log^2n\)。虽然看上去不能过但跑起来还是蛮快的。

Code:

#include<bits/stdc++.h>
#define ls tr<<1
#define rs tr<<1|1
using namespace std;
const int N = 800040;

inline int read(){
	int x = 0, f = 1; char ch = getchar();
	while(ch<'0' || ch>'9'){
		if(ch == '-'){
			f = -1; 
		}
		ch = getchar();
	}
	while(ch>='0'&&ch<='9'){
		x = x*10+ch-48;
		ch = getchar();
	}
	return x*f;
}

//—————————— 
int tree[N<<2];
void push_up(int tr){
	tree[tr] = tree[ls]+tree[rs];
}
void change(int tr, int L, int R, int pos, int val){
	if(L == R){
		tree[tr]+=val;
		return;
	}
	int mid = (L+R)>>1;
	if(pos <= mid){
		change(ls, L, mid, pos, val);
	}
	else{
		change(rs, mid+1, R, pos, val);
	}
	push_up(tr);
}
int query(int tr, int L, int R, int lq, int rq){
	if(R<lq || L>rq){
		return 0;
	}
	if(lq<=L&&R<=rq){
		return tree[tr];
	}
	int mid = (L+R)>>1;
	int ret = 0;
	ret+=query(ls, L, mid, lq, rq);
	ret+=query(rs, mid+1, R, lq, rq);
	return ret;
}
//—————————— 
int head[N], tot;
struct node{
	int nxt, to;
}edge[N<<1];
void add(int u, int v){
	edge[++tot].nxt = head[u];
	edge[tot].to = v;
	head[u] = tot;
}
int n, Q;

int siz[N], son[N], fa[N], dfn[N], idx, top[N], dep[N], fdfn[N];
void dfs1(int u, int fath){
	siz[u] = 1;
	dep[u] = dep[fath]+1;
	fa[u] = fath;
	for(int i = head[u]; i; i = edge[i].nxt){
		int v = edge[i].to;
		if(v == fath){
			continue;
		}
		dfs1(v, u);
		siz[u]+=siz[v];
		if(siz[son[u]]<siz[v]){
			son[u] = v;
		}
	}
}
void dfs2(int u, int Top){
	top[u] = Top;
	dfn[u] = ++idx;
	fdfn[idx] = u;
	if(!son[u]){
		return;
	}
	dfs2(son[u], Top);
	for(int i = head[u]; i; i = edge[i].nxt){
		int v = edge[i].to;
		if(!dfn[v]){
			dfs2(v, v);
		}
	}
}
int query_lca(int x, int y){
	while(top[x] ^ top[y]){
		if(dep[top[x]]<dep[top[y]]){
			swap(x, y);
		}
		x = fa[top[x]];
	}
	if(dep[x]>dep[y]){
		swap(x, y);
	}
	return x;
}

int all;
bool che[N];
int bisearch(int l, int r){
	int ret, tmp;
	while(l<=r){
		int mid = (l+r)>>1;
		tmp = query(1, 1, n, mid, mid+siz[fdfn[mid]]-1);
		if(tmp){
			l = mid+1;
			ret = mid;
		}
		else{
			r = mid-1;
		}
	}
	return fdfn[ret];
}
int solve(int x){
	if(all == 0){
		return 0;
	}
	int tmp = query(1, 1, n, dfn[x], dfn[x]+siz[x]-1);
	if(tmp){
		return x;
	}
	while(top[x]^1){
		tmp = query(1, 1, n, dfn[top[x]], dfn[top[x]]+siz[top[x]]-1 );
		if(tmp){
			return bisearch(dfn[top[x]], dfn[x]);
		}
		x = fa[top[x]];
	}
	return bisearch(dfn[1], dfn[x]);
}
int main(){
	n = read(), Q = read();
	for(int i = 1;i<n; i++){
		int u = read();
		add(u, i+1);
		add(i+1, u);
	}
	dfs1(1, 0);
	dfs2(1, 1);
	int op, tmp;
	while(Q--){
		 op = read();
		if(op>0){
			if(che[op]){
				che[op] = 0;
				all--;
				change(1, 1, n, dfn[op], -1);
			}
			else{
				che[op] = 1;
				all++;
				change(1, 1, n, dfn[op], 1);
			}
		}
		else{
			op = -op;
			 tmp = solve(op);
			printf("%d\n", tmp);
		}
	}
	return 0;
}

T4树

啊啊啊这个题。并查集真的生疏了qwq。

其实还是蛮绕的。我们发现需要确定方向,可以考虑用并查集。我们将边分为上向边和下向边,并将所有边下放到点上。每次就将同一个方向的边放到同一集合中(这个说法不太准确,一会儿会再解释),最后统计一下集合数量。因为同一集合里,边是可以统一颠倒方向的,故最后的答案就是 \(2^{num}\)\(num\) 为集合数。

但是,这里会发现一个问题。因为它既可以正着连也可以反着连,会乱。所以,这里可以考虑使用扩展域并查集,即上向边为 \(x\) ,下向边为 \(x+n\)。这样,向上连的集合和向下连的集合就可以分开了(还是不准确,一会儿说)。连边分为两种情况:

其中一点是两点的lca

这个情况直接连即可。注意要把路径上的所有边纳入一个集合。

若两点的lca不是任何一个点

这种情况下,我们会发现从一个点到另一个点,一定会先走一段上向边,再走一段下向边。所以,在向lca连完后,还要把两个点对应边的上向边集合向对方的下向边集合连边。这里就发现了之前对于集合的定义是不准确的,因为一个集合里会有不同方向的边。其实这里的集合表示的是一些路径,而这些路径中的边都是定向的。

注意,这样的话最后会有两组数量一样的集合,所有最后的 \(num\) 应除以 \(2\)

判断无解也很好说了。如果一条边的上向边与下向边属于一个集合,就说明这个边是矛盾的。因为它不可能在这个路径中既向上又向下。

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9+7;
const int N = 300030; 

inline int read(){
	int x = 0, f = 1; char ch = getchar();
	while(ch<'0' || ch>'9'){
		if(ch == '-'){
			f = -1; 
		}
		ch = getchar();
	}
	while(ch>='0'&&ch<='9'){
		x = x*10+ch-48;
		ch = getchar();
	}
	return x*f;
}

int head[N], tot;
struct node{
	int nxt, to;
}edge[N<<1];
void add(int u, int v){
	edge[++tot].nxt = head[u];
	edge[tot].to = v;
	head[u] = tot;
}
int siz[N], fa[N], son[N], idx, dfn[N], top[N], dep[N];
void dfs1(int u, int fath){
	fa[u] = fath;
	dep[u] = dep[fath]+1;
	siz[u] = 1;
	for(int i = head[u]; i; i = edge[i].nxt){
		int v = edge[i].to;
		if(fath == v){
			continue;
		}
		dfs1(v, u);
		siz[u]+=siz[v];
		if(siz[son[u]]<siz[v]){
			son[u] = v;
		}
	}
}
void dfs2(int u, int Top){
	dfn[u] = ++idx;
	top[u] = Top;
	if(!son[u]){
		return;
	}
	dfs2(son[u], Top);
	for(int i = head[u]; i; i = edge[i].nxt){
		int v = edge[i].to;
		if(!dfn[v]){
			dfs2(v, v);
		}
	}
}
int query_lca(int x, int y){
	while(top[x] ^ top[y]){
		if(dep[top[x]]<dep[top[y]]){
			swap(x, y);
		}
		x = fa[top[x]];
	}
	if(dep[x]>dep[y]){
		swap(x, y);
	}
	return x;
}

int f[N<<1];
int n, m;
int find(int x){
	if(x ^ f[x]){
		f[x] = find(f[x]);
	}
	return f[x];
}
void addup(int x, int y){//n 
	while(dep[fa[x]]>dep[y]){
		int p = find(x);
		int d = find(fa[x]);
		int q = find(x+n);
		int b = find(fa[x]+n);
		f[p] = d;
		f[q] = b;
		x = d;
	}
}

int ans;
inline int qpow(int a, int b){
	int ret = 1;
	while(b){
		if(b & 1){
			ret = (1ll*ret*a)%mod; 
		}
		b>>=1;
		a = (1ll*a*a)%mod;
	}
	return ret;
}
int qwq[2][N];
int lca[N];
int main(){
	
	n = read(), m = read();
	for(int i = 1; i<n; i++){
		int u = read(), v = read();
		add(u, v);
		add(v, u);
	}
	dfs1(1, 0);
	dfs2(1, 1);
	for(int i = 1; i<=n*2; i++){
		f[i] = i;
	}
	for(int i = 1; i<=m; i++){
		qwq[0][i]  = read(), qwq[1][i] = read();
		lca[i] = query_lca(qwq[0][i], qwq[1][i]);
			addup(qwq[0][i], lca[i]);
			addup(qwq[1][i], lca[i]);
		
	}
	for(int i = 1; i<=m; i++){//关于这一步为什么要另外操作,因为前面需要通过并查集跳来保证合并复杂度,而这时候将横跨祖先的边合并会使跳父亲的时候出错。
		int x = qwq[0][i], y = qwq[1][i];
		if((x^lca[i])&&(y^lca[i])){
			f[find(x+n)] = find(y);
			f[find(y+n)] = find(x);
		}
	}
	for(int i = 2; i<=n; i++){
		int p = find(i), q = find(i+n);
		if(p ^ q){
			if(p == i){
				ans++;
			}
			if(q == i+n){
				ans++;
			}
		}
		else{
			puts("0");
			return 0;
		}
	}
	ans = qpow(2, ans/2);
	printf("%d\n", ans);
	return 0;
}

posted @ 2023-06-07 10:49  霜木_Atomic  阅读(10)  评论(0编辑  收藏  举报