暑假集训D4 2023.7.27 补题

昨天做搜索专题真是太折磨了,总是想不到.今天比昨天稍微好一点,但也没好哪去.

H. P2504 [HAOI2006] 聪明的猴子

这题虽然最后 \(AC\) 了,但是中途做了好几种方法都没成功.

首先是把点的坐标转化成距离,给出来的点两两组合就好了.注意是双向的. \(i \rightarrow j\) $\ \ \ $ \(j \rightarrow i\)

然后就是求解过程了

首先想用类似于 \(dijkstra\) 的方法做的.

if(dist[j]>w[i])
{
	dist[j] = w[i];
	q.push({dist[j],j});
}

先求出到点 \(i\) 的最短的边权 ,记为 \(dist[i]\) ,然后对所有点的权 \(dist\)\(\max\) .

double res = -1;
for(int i =1;i<=n;i++)
{
	res = max(res,dist[i]);
}
return res;

很不幸的 \(WA\) 了,后面有时间写对拍改一下.

本来想的是,对于一个点 \(j\) ,如果他能到其他任意的一个点,最短的一条边边权为 \(w\) ,就记为 \(dist[j]\) ,然后统计所有点的 \(dist\) ,取 \(\min\) 作为答案.比如 \(1\) 有边直接连接到 \(2\ 3\ 4\) 三个点, 距离分别为 \(4\ 5\ 2\),只要青蛙能跳 \(2\) 的距离,那么青蛙就可以从 \(4 \rightarrow 1\) , 然而这可以被 \(hack\) 掉:

image

青蛙想觅食必须得通过 \(1 \rightarrow 3\) 这条边. 因此最短距离必须是3

正解是用 \(Kruskal\) 算法. \(n\) 个点连通后最大的那条边即为答案

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<math.h>
#include<queue>
#define endl '\n'
using namespace std;
const int N = 2e6+10;
double w[N];
struct edge
{
	int a,b;
	double c;
	bool operator<(edge &b)&
{
	return c<b.c;
}
}e[N];
int idx ;
int f[N];
int find(int x)
{
	if(x!=f[x]) f[x] = find(f[x]);
	return f[x];
}
typedef pair<double,int> PII;
int n;
PII node[N];
double dis(PII a,PII b)
{
	return sqrt((a.first-b.first)*(a.first-b.first)+(a.second-b.second)*(a.second-b.second));
}
double mouse[N];
int m;
int main()
{
	
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>m;
	for(int i= 1;i<=m;i++)
	{
		cin>>mouse[i];
	}
	sort(mouse+1,mouse+m+1);
	cin>>n;
	for(int i = 1;i<=n;i++)
	{
		f[i] = i;
	}
	
	for(int i =1;i<=n;i++)
	{
		int x,y;
		cin>>x>>y;
		node[i]={x,y};
	}
	for(int i =1;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			e[idx++]= {i,j,dis(node[i],node[j])};
		}
	}
	sort(e,e+idx);
	double max_dis = -1;
	for(int i=0;i<idx;i++)
	{
		int a = e[i].a;
		int b = e[i].b;
		int pa = find(a);
		int pb = find(b);
		if(pa!=pb)
		{
			f[pa] = pb;
			max_dis = max(max_dis,e[i].c);
		}
	}
	
	int pos = ((lower_bound(mouse+1,mouse+m+1,max_dis)-mouse-1));
	cout<<m-pos;
	
	return 0;
}

D. P1268 树的重量

给定一颗树的距离矩阵,求出这个树的重量(所有边权之和)
image

I. P8625 [蓝桥杯 2015 省 B] 生命之树

在一棵树中选出一些点集,该点集必须满足

  • 任意两点都存在路径
  • 路径上的点都 \(\in\) 该点集.

树的每个点都有一个权值.定义该点集的评分为该点集内所有点之和.
现在给出一颗树,找出评分最大的点集.

这题看了很久没什么思路,后来看了一下题目的 \(tag\)树型 \(DP\) ,然后去 \(OIwiki\) 上查了一些资料.发现这题其实不难.

\(OIwiki\) 上的第一句话就给了我这道题的思路

树形 DP,即在树上进行的 DP。由于树固有的递归性质,树形 DP 一般都是递归进行的

定义 \(dp[i]\) 是以 \(i\) 为根的一颗子树所拥有的最大评分. 设 \(j\) 是节点 \(i\) 的孩子结点.
那么如果节点 \(j\)\(i\) 的直系儿子并且点权大于 \(0\) ,就应该把他算在结点 \(i\) 的评分中.所以只要递归地去处理即可!

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#define endl '\n'
#define pb push_back
#define int long long
using namespace std;
const int N = 1e5+10;
int dp[N];
int vis[N];
int a[N];
vector<int> v[N];
int n;
vector<int>res(N);
int dfs(int u,int sum,int step)
{
	res[step] = u;
	if(dp[u])return dp[u];//只需要算一次,以前算过一遍则直接返回即可.
	vis[u] = 1;
	
	dp[u] = a[u];
	int siz = v[u].size();
	for(int i = 0;i<siz;i++)
	{
		int j = v[u][i];
		if(vis[j])continue;
		
		vis[j] =1;
		if(dfs(j,sum+a[j],step+1)>0)
		{
			dp[u]+=dp[j];
		}
		vis[j] = 0;
	}
	return dp[u];
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	for(int i =1;i<=n-1;i++)
	{
		int a,b;
		cin>>a>>b;
		v[a].pb(b);
		v[b].pb(a);
		
	}
	int res = 0;
	for(int i =1;i<=n;i++)
	{
		res = max(res,dfs(i,0,0));
	}
	cout<<res<<endl;
	return 0;
}

C.P9304 「DTOI-5」3-1

给出一颗由 \(n-1\) 条边连通的树,树中每条边都是双向边,边权为 \(1\) ,同时有额外 \(n-1\) 个传送门,可以将 \(2\sim n\) 无花费地瞬时传到根节点 \(1\) ,问如果要访问 \(k\) 个节点,最小花费是多少? (\(k\in [1,n]\))

\(\operatorname{Solution}\)

首先对于询问 \(k\) ,由于开始时已经在 \(1\) 号节点 ,已经访问了 \(1\) 个节点了,所以开始时就可以让 \(k\) 减去 \(1\) .下面的讨论都基于此.

设从 \(1\) 走到最远的一条不折返路径长度为 \(d\) ,那么对于询问 \(k\)

  • \(k\leq d\) 时,立即可以得到最小花费为 \(k\). 解法是可以直接沿着这条路径走下去,访问足够数量以后传送回 \(1\) 即可,花费为 \(k\).
  • \(k>d\) 时,只走一条最长路径显然是不够的,因此一定需要折返走一些小分支.
    \(\qquad\) 1. 小分支是最长路径的一个分支.不妨先去访问小分支,然后折返再走最长路径,最后传送回 \(1\) ,显然额外花费了 \(2*\)小分支上的结点数.
    \(\qquad\) 2. 小分支是从根节点 \(1\) 分出去的.可以先访问完最长路径后,传送回 \(1\) ,然后走小分支,最后折返回 \(1\) ,此时走小分支的额外花费也为 \(2*\)小分支上的结点数.
    \(\qquad\) 因此得到结论:不在最长路径上的结点每次访问的花费总是 \(2\) .故此时花费为 \(d\) + \((k-d)*2\)
for(int k = 1;k<=n;k++)
{
	int t = k-1;
	int res = 0;
	if(t<=m_dep)
	{
		res = t;
	}
	else
	{
		res = m_dep;
		res+=(t-m_dep)*2;
	}
	cout<<res<<endl;
}
#include<stdio.h>
#include<iostream>
#include<algorithm>
#define endl '\n'
#define pb push_back
using namespace std;
typedef pair<int,int> PII;
const int N = 1e5+10;
vector<int> v[N];
int vis[N];
int get_m_depth(int u,int depth)
{
	int sum = depth;
	vis[u] = true;
	int sz = v[u].size();
	int res = depth;
	for(int i = 0;i<sz;i++)
	{
		int j = v[u][i];
		if(!vis[j])
		res  = max(res,get_m_depth(j,sum+1));
	}
	return res;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n;
	cin>>n;
	for(int i =1;i<=n-1;i++)
	{
		int a,b;
		cin>>a>>b;
		v[a].pb(b);
		v[b].pb(a);
	}
	int m_dep=get_m_depth(1,0);
	//cout<<m_dep;
	
	for(int k = 1;k<=n;k++)
	{
		int t = k-1;
		int res = 0;
		if(t<=m_dep)
		{
			res = t;
		}
		else
		{
			res = m_dep;
			res+=(t-m_dep)*2;
		}
		cout<<res<<endl;
	}
	
	return 0;
}
posted @ 2023-07-27 21:06  LZH_03  阅读(7)  评论(0编辑  收藏  举报