2022牛客暑期多校训练营3

A( L C A LCA LCA) C(排序) J( 01 B F S 01BFS 01BFS)
A - Ancestor

题意

给出两棵编号 1 − n 1-n 1n 的树 A A A B B B A A A B B B 树上每个节点均有一个权值。

给出 k k k 个关键点的编号 x 1 … x n x_1…x_n x1xn

问有多少种方案使得去掉恰好一个关键点使得剩余关键点在树 A A A L C A LCA LCA的权值大于 B B B L C A LCA LCA的权值。

思路

由于 L C A LCA LCA 满足结合律,即 l c a ( a , b , c ) = l c a ( l c a ( a , b ) , l c a ( b , c ) ) lca(a,b,c)=lca(lca(a,b),lca(b,c)) lca(a,b,c)=lca(lca(a,b),lca(b,c))

预处理出关键点序列的在树A B上的前缀 L C A LCA LCA 和后缀 L C A LCA LCA

枚举去掉的关键节点,若为 i i i,则剩余点的 l c a lca lca l c a ( p r e [ i − 1 ] , l a s t [ i + 1 ] ) lca(pre[i-1],last[i+1]) lca(pre[i1],last[i+1])

#include<bits/stdc++.h>
using namespace std;

const int N = 100010, M = 2 * N;
int n, k;
int a[N], b[N], x[N];
int ha[N], ea[M], nea[M], idxa;
int hb[N], eb[M], neb[M], idxb;
int prea[N], lasta[N], preb[N], lastb[N];
int depa[N], depb[N];
int fa[N][20], fb[N][20];
queue<int> q;
void add_a(int a, int b)
{
	ea[idxa] = b, nea[idxa] = ha[a], ha[a] = idxa++;
}
void add_b(int a, int b)
{
	eb[idxb] = b, neb[idxb] = hb[a], hb[a] = idxb++;
}

void bfs()
{
	// A
	q.push(1), depa[1] = 1;
	while(q.size())
	{
		int x = q.front();
		q.pop();
		for(int i = ha[x]; ~i; i = nea[i])
		{
			int y = ea[i];
			if(depa[y]) continue;
			depa[y] = depa[x] + 1;
			fa[y][0] = x;
			for(int j = 1; j <= 16; j++) fa[y][j] = fa[fa[y][j - 1]][j - 1];
			q.push(y);
		}
	}
	// B
	q.push(1), depb[1] = 1;
	while(q.size())
	{
		int x = q.front();
		q.pop();
		for(int i = hb[x]; ~i; i = neb[i])
		{
			int y = eb[i];
			if(depb[y]) continue;
			depb[y] = depb[x] + 1;
			fb[y][0] = x;
			for(int j = 1; j <= 16; j++) fb[y][j] = fb[fb[y][j - 1]][j - 1];
			q.push(y);
		}
	}
}
int lca_a(int x, int y)
{
	if(depa[x] > depa[y]) swap(x, y);
	for(int i = 16; i >= 0; i--) 
		if(depa[fa[y][i]] >= depa[x]) y = fa[y][i];
	if(x == y) return x;
	for(int i = 16; i >= 0; i--) 
		if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
	return fa[x][0];
}
int lca_b(int x, int y)
{
	if(depb[x] > depb[y]) swap(x, y);
	for(int i = 16; i >= 0; i--) 
		if(depb[fb[y][i]] >= depb[x]) y = fb[y][i];
	if(x == y) return x;
	for(int i = 16; i >= 0; i--) 
		if(fb[x][i] != fb[y][i]) x = fb[x][i], y = fb[y][i];
	return fb[x][0];
}
void init()
{
	// A
	prea[1] = x[1], lasta[k] = x[k]; 
	for(int i = 2; i <= k; i++)	prea[i] = lca_a(prea[i - 1], x[i]);
	for(int i = k - 1; i >= 1; i--) lasta[i] = lca_a(lasta[i + 1], x[i]);
	// B
	preb[1] = x[1], lastb[k] = x[k]; 
	for(int i = 2; i <= k; i++) preb[i] = lca_b(preb[i - 1], x[i]);
	for(int i = k - 1; i >= 1; i--) lastb[i] = lca_b(lastb[i + 1], x[i]);
}
void solve()
{
	memset(ha, -1, sizeof ha);
	memset(hb, -1, sizeof hb);	
	cin >> n >> k;
	for(int i = 1; i <= k; i++) cin >> x[i];
	int u;
	// A
	for(int i = 1; i <= n; i++) cin >> a[i];
	for(int i = 2; i <= n; i++)
	{
		cin >> u;
		add_a(i, u), add_a(u, i);
	}
	// B
	for(int i = 1; i <= n; i++) cin >> b[i];
	for(int i = 2; i <= n; i++)
	{
		cin >> u;
		add_b(i, u), add_b(u, i);
	}
	bfs();
	init();
	int res = 0;
	for(int i = 1; i <= k; i++)
	{
		int wa, wb;
		if(i == 1)
		{
			wa = a[lasta[2]];
			wb = b[lastb[2]];
		}else if(i == k)
		{
			wa = a[prea[k - 1]];
			wb = b[preb[k - 1]];
		}else{
			wa = a[lca_a(prea[i - 1], lasta[i + 1])];
			wb = b[lca_b(preb[i - 1], lastb[i + 1])];
		}
		
//		cout << wa << " " << wb << endl;
		if(wa > wb) res++;
	}
	cout << res << endl;
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int T;
//	cin >> T;
//	while(T--)
	{
		solve();
	}
	return 0;
}
C - Concatenation

题意

给定 n n n 个字符串,重新排列他们,使得最后组成的字符串的字典序最小。

思路

a + b < b + a a+b<b+a a+b<b+a 排序

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e6 + 20;
string a[N];

bool cmp(const string &a, const string &b)
{
    return a + b < b + a;
}
void solve()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; ++i)
        cin >> a[i];
    sort(a, a + n, cmp);
    for (int i = 0; i < n; ++i)
        cout << a[i];
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}
J - Journey

思路

双端队列, 01 b f s 01bfs 01bfs

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
typedef pair<int,int> PII;
const int N = 500010, INF = 0x3f3f3f3f;
int n, cnt;
PII ss, tt;
PII a[N][5];

void solve()
{
	cin >> n;
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= 4; j++)
		{
			cin >> a[i][j].first;
			if(a[i][j].first) a[i][j].second = ++cnt;
		}
	}
	cin >> ss.first >> ss.second >> tt.first >> tt.second;
	
	vector<vector<PII>> g(cnt + 1, vector<PII>(0));
	for(int u = 1; u <= n; u++)
	{
		for(int j = 1; j <= 4; j++)
		{
			if(!a[u][j].first) continue;
			int v = a[u][j].first;
			int dir;
			for(int k = 1; k <= 4; k++)
			{
				if(u == a[v][k].first) dir = k;
			}
			dir = dir % 4 + 1;
			for(int k = 1; k <= 4; k++)
			{
				if(!a[v][k].first) continue;
				if(dir == k) g[a[u][j].second].push_back({a[v][k].second, 0});
				else g[a[u][j].second].push_back({a[v][k].second, 1});
			}
		}
	}
	int s, t;
	for(int i = 1; i <= 4; i++)
		if(a[ss.first][i].first == ss.second) s = a[ss.first][i].second;
	for(int i = 1; i <= 4; i++)
		if(a[tt.first][i].first == tt.second) t = a[tt.first][i].second;
	
	deque<PII> q;
	vector<int> dist(cnt + 1, INF);
	vector<bool> st(cnt + 1, 0);
	q.push_back({s, 0});
	dist[s] = 0;
	while(q.size())
	{
		int u = q.front().first;
		int d = q.front().second;
		q.pop_front();
		if(st[u]) continue;
		st[u] = true;
		dist[u] = d;
		
		for(auto it : g[u])
		{
			int v = it.first, w = it.second;
			if(w == 0) q.push_front({v, d});
			else q.push_back({v, d + 1});
				
		}
	}
	if(dist[t] > INF / 2) cout << -1 << endl;
	else cout << dist[t] << endl;
	
	return;
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int T = 1;
//	cin >> T;
	while(T--)
	{
		solve();
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值