2024暑假牛客多校题解4

A LCT

题意:给出n-1条边,每条边后面紧随一个提问,求以c为根的树的最长路径

思路:由于题目给了空间限制,不能把所有的树都存下来去找,因此我们需要一个空间复杂度O(1)的解法,这边可以考虑采用带权并查集去解决这个问题,具体实现思路由代码以及注释给出。

代码:

#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<queue>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
#define ll long long

const ll N = 1e6 + 10;

int fa[N] = { 0 }; //fa[i]记录i结点的最终父结点
int dep[N] = { 0 }; //dep[i]表示i结点在自己所在树中的深度,默认根结点的深度为0
int lon[N] = { 0 }; //lon[i]表示以i结点为根的树的最长路径上包括几个点,最终输出答案的时候记得-1,因为求的是路径长度,是路径结点数-1

int find(int x)
{
	if (fa[x] != x) //如果这个结点不是最终根结点,进行更新
	{
		lon[fa[x]] = max(lon[fa[x]], lon[x] + dep[x]); //这个结点最终根结点的值就是当前结点为根的最长路径结点数加上这个结点的深度,也就是这个结点上面还有几个结点。
		//一开始肯定是一层层更新的,不能一开始就直接去更新最终的父亲结点的值,否则中间部分的答案就会缺失。
		int f = find(fa[x]); //递归更新当前结点上面几层,直到更新到最终根为止
		dep[x] += dep[fa[x]]; //这个结点的深度就是父节点的深度加上本身的1.
		fa[x] = f; //更新一下最终父结点
	}
	return fa[x];
}

void solve()
{
	ll n;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		fa[i] = i;
		dep[i] = 0; //一开始只有根结点的时候深度为0
		lon[i] = 1;
	}
	for (int i = 1; i < n; i++)
	{
		int a, b, c;
		cin >> a >> b >> c;
		fa[b] = a;
		dep[b] = 1; //当加了一个结点的时候我们先设这个点的深度为一,以便之后更新
		int p = find(b); //更新一下各个点的答案
		cout << lon[c]-1; //输出答案即可
		if (i == n - 1)
		{
			cout << endl;
		}
		else
		{
			cout << ' ';
		}
	}
}

signed main()
{
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	ll t;
	cin >> t;
	while (t--)
	{
		solve();
	}
}

C sort 4

题意:给出一个大小为n的乱序数组,每次选4个数进行随机排序,直到变成你想要的状态停止,这样算一次操作,问最少几次操作可以使得数组变为有序的。

思路:首先我们发现,如果按照乱序数组一个个找对应位置,会找到许多大小不1的环,那么我们可以根据环的大小划分如何操作,对于大小为1的,就是在对应位置上的,明显不用操作,否则打乱了麻烦,对于大小为2的,我们可以把2组拼凑在一起,来减小操作次数。对于大小为3,4的明显无法组合肯定要一个操作次数,至于大于等于5的环,尽可能3个3个归位,最后不足5个的判断是2还是3,4就行

代码:

#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<queue>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
#define ll long long

const ll N = 1e6 + 10;

int f[N] = { 0 };
int arr[N] = { 0 };

void solve()
{
	ll n;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		f[i] = 0;
		cin >> arr[i];
	}
	int ans = 0;
	int sum2 = 0;
	for (int i = 1; i <= n; i++)
	{
		ll now = arr[i];
		ll shu = 0;
		while (!f[now]) 
		{
			f[now] = 1;
			now = arr[now];
			shu++;
		}
		if (shu == 2)
		{
			sum2++;
		}
		else if (shu == 3 || shu == 4)
		{
			ans++;
		}
		else if (shu>=5)
		{
			while (shu > 4)
			{
				ans++;
				shu -= 3;
			}
			if (shu != 2)
			{
				ans++;
			}
			else
			{
				sum2++;
			}
		}
	}
	ans += (sum2 + 1) / 2;
	cout << ans << endl;
}

signed main()
{
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	ll t;
	cin >> t;
	while (t--)
	{
		solve();
	}
}

F Good Tree

题意,给出一个x,问最小需要几个结点构成树使得树上存在一组u,v。使得f(u)-f(v)=x,f(x)表示从x出发到任意一个结点的路径和。

思路:稍微打一点表或者手动推几组数据会发现答案是根据x增大而增大,与x成正比的,对于n个结点,我们又可以推出n个结点的最大路径和最小路径,那么在这个差值范围内的值基本都是可以取到的。这就是我们二分的判定条件,然后就是推几组的时候会发现有一些特殊情况,我们的目的就是找出这些特殊情况。我们发现如果按照正比例推出的答案是偶数的话,如果x要求是奇数,那肯定是没有办法组合出这个x的,这种情况需要特判一下。

代码:

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

const ll N = 1e6 + 10;

int f[N] = { 0 };
int arr[N] = { 0 };

ll check(ll mid, ll x)
{
	ll sum = 0;
	if (mid == 3)
	{
		sum = 1;
	}
	else if (mid % 2)
	{
		ll high = (mid - 1) * mid / 2;
		ll low = (mid / 2 + 1) * (mid / 2);
		sum = high - low;
	}
	else
	{
		ll high = (mid - 1) * mid / 2;
		ll low = (mid / 2 + 1) * (mid / 2) / 2 + (mid / 2) * (mid / 2 - 1) / 2;
		sum = high - low;
	}
	return sum >= x;
}


void solve()
{
	ll x;
	cin >> x;
	ll l = 3;
	ll r = INT_MAX;
	ll ans = INT_MAX;
	while (l <= r)
	{
		ll mid = (l + r)/2;
		if (check(mid, x)) r = mid - 1,ans=min(ans,mid);
		else l = mid + 1;
	}
	if (x % 2 && ans % 2 == 0)
	{
		ans++;
	}
	cout << ans << endl;
}

signed main()
{
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	ll t;
	cin >> t;
	while (t--)
	{
		solve();
	}
}

G Horse Drinnks Water

题意:将军饮马问题,给出x和y轴,求最小距离

思路:签到题,按照将军饮马思路对x对称,y对称,按照公式求一下取最小值即可。

H Yet Another Origami Problem

题意:给出一个数组,你可以取一个值Ap进行操作,可以选择两种操作,让小于Ap的Ai变成2Ap-Ai,或者让大于Ap的Ai变成2Ap-Ai。求最小的极差

思路:猜猜题,或者自己找规律证明,赛时推几组猜的结论,答案是排序后相邻两个数的差的最大公约数,gcd。具体证明可以去看官方题解证明思路,讲的很好。

I Friends

题意:给出n个人,m条关系,问有几种区间l-r,使得这个区间内任意两个人都是朋友关系,朋友关系不能间接,只能直接,一个人的区间也满足条件

思路:对于任意一个区间我们考虑,如果l - r是一个满足条件的区间,那这个区间内所有的子区间肯定也是满足条件的区间。如果对于r+1来说,l到r都和r+1有朋友关系,那么l-r的右区间就可以扩大,否则就直接把l跳到r+1的位置继续去找即可。

代码:

#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<queue>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
#define ll long long

const ll N = 1e6 + 10;


void solve()
{
	ll n, m;
	cin >> n >> m;
	map<pair<int,int>, int> M;
	for (int i = 1; i <= m; i++)
	{
		ll l, r;
		cin >> l >> r;
		M[{l, r}] = 1;
	}
	ll ans = n;
	ll i = 1;
	ll j = 2;
	while (j <= n)
	{
		if (i == j)
		{
			j++;
			continue;
		}
		int f = 1;
		for (int k = j-1; k >= i; k--)
		{
			if (M[{k, j}] == 0)
			{
				f = 0;
				i = k + 1;
				j--;
				break;
			}
		}
		if (f)
		{
			ans += j - i;
		}
		j++;
	}
	cout << ans << endl;
}

signed main()
{
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	ll t;
	t = 1;
	while (t--)
	{
		solve();
	}
}

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值