暑假集训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\) 掉:
青蛙想觅食必须得通过 \(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 树的重量
给定一颗树的距离矩阵,求出这个树的重量(所有边权之和)
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;
}