洛谷每日一练5.11--P4145+P2015+P2195(图+线段树+DP)

P4145

题意:

给 一 个 长 度 为 n 的 数 列 , 有 两 个 操 作 : 给一个长度为n的数列,有两个操作: n
① 将 [ L , R ] 内 的 数 都 取 平 方 根 ( 向 下 取 整 ) ①将[L,R]内的数都取平方根(向下取整) [L,R]()
② 求 [ L , R ] 的 区 间 和 ②求[L,R]的区间和 [L,R]

思路:

暴 力 单 点 修 改 可 过 暴力单点修改可过
可 知 若 对 1 取 平 方 根 也 是 1 , 可 以 把 这 种 操 作 省 略 可知若对1取平方根也是1,可以把这种操作省略 11
当 要 修 改 这 个 区 间 时 , 若 该 区 间 最 大 值 小 于 等 于 1 , 则 不 修 改 当要修改这个区间时,若该区间最大值小于等于1,则不修改 1

#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = 1e5+10;
int n,q;
ll a[N];
struct Node{
	int l,r;
	ll sum,mx;
}tr[N<<2];
void pushup(int p){
	tr[p].mx = max(tr[p<<1].mx,tr[p<<1|1].mx);
	tr[p].sum = tr[p<<1].sum + tr[p<<1|1].sum;
}
void build(int p,int l,int r){
	tr[p].l = l,tr[p].r = r;
	if(l == r){
		tr[p].sum = tr[p].mx = a[l];
		return;
	}
	int mid = l + r >> 1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	pushup(p);
}
ll query(int p,int L,int R){
	int l = tr[p].l,r = tr[p].r;
	if(l >= L&& r <= R) return tr[p].sum;
	int mid = l + r >> 1;
	ll ans = 0;
	if(L <= mid) ans += query(p<<1,L,R);
	if(R > mid) ans += query(p<<1|1,L,R);
	return ans;
}
void update(int p,int L,int R){
	int l = tr[p].l,r = tr[p].r;
	if(l == r){
		tr[p].sum = tr[p].mx = sqrt(tr[p].sum);
		return;
	}
	int mid = l + r >> 1;
	if(L <= mid && tr[p<<1].mx > 1) update(p<<1,L,R);
	if(R > mid && tr[p<<1|1].mx > 1) update(p<<1|1,L,R);
	pushup(p);
}
int main(){
	scanf("%d",&n);
	for(int i = 1;i <= n;i++) scanf("%lld",a+i);
	build(1,1,n);
	scanf("%d",&q);
	while(q--){
		int op,l,r;scanf("%d%d%d",&op,&l,&r);
		if(l > r) swap(l,r);
		if(op == 0) update(1,l,r);
		else printf("%lld\n",query(1,l,r));
	}
	return 0;
}

P2015

题意:

给 一 颗 以 1 为 根 的 树 给 你 , 每 条 树 边 都 有 一 个 权 重 给一颗以1为根的树给你,每条树边都有一个权重 1
要 求 你 把 这 颗 以 1 为 根 的 树 砍 剩 下 m 条 边 , 使 得 这 棵 树 边 权 之 和 最 大 要求你把这颗以1为根的树砍剩下m条边,使得这棵树边权之和最大 1m,使

思路:

和 P 2014 一 样 , 树 形 背 包 , 不 过 这 题 权 重 在 边 上 , 而 不 是 在 点 和P2014一样,树形背包,不过这题权重在边上,而不是在点 P2014
注 意 转 移 方 程 的 不 同 注意转移方程的不同

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 105;
int n,m,head[N],idx;
struct Node{
	int to,nex,w;
}e[N<<1];
void add_edge(int u,int v,int w){
	e[idx].to = v;
	e[idx].nex = head[u];
	e[idx].w = w;
	head[u] = idx++;
}
int f[N][N];//f(i,j)表示只考虑以i为根节点的子树并且背包容量为j的最大价值 
void dfs(int u,int fa){
	for(int i = head[u];~i;i = e[i].nex){
		int v = e[i].to;
		if(v == fa) continue;
		dfs(v,u);
		//倒序枚举容量避免重复计算一个子节点的值
		for(int j = m;j >= 1;j--){
			//枚举子节点选取边的数量
			for(int k = 0;k < j;k++){
				f[u][j] = max(f[u][j],f[u][j-k-1]+f[v][k]+e[i].w);
			} 
		} 
	}
} 
int main(){
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m);
	for(int i = 1,u,v,w;i < n;i++){
		scanf("%d%d%d",&u,&v,&w);
		add_edge(u,v,w);add_edge(v,u,w);
	}
	dfs(1,0);
	printf("%d\n",f[1][m]);
	return 0;
}

P2195

题意:

给 一 个 有 n 个 点 , m 条 边 的 森 林 , 有 q 组 操 作 给一个有n个点,m条边的森林,有q组操作 nmq
有 两 种 类 型 的 操 作 : 有两种类型的操作:
① ① 1 1 1 x x x : 询 问 x 所 在 树 的 直 径 :询问x所在树的直径 x
② ② 2 2 2 x x x y y y : 若 x 和 y 本 来 在 一 棵 树 忽 略 这 次 操 作 , 否 则 将 x 所 在 树 和 y 所 在 树 :若x和y本来在一棵树忽略这次操作,否则将x所在树和y所在树 xyxy
用 一 条 边 连 成 一 颗 新 树 , 并 要 求 这 颗 新 树 的 直 径 在 所 有 方 案 中 最 小 用一条边连成一颗新树,并要求这颗新树的直径在所有方案中最小

思路:

首 先 对 于 一 棵 树 的 用 并 查 集 连 起 来 , 并 且 求 出 每 颗 树 的 直 径 ; 重 点 在 于 第 二 个 操 作 首先对于一棵树的用并查集连起来,并且求出每颗树的直径;重点在于第二个操作
如 果 让 新 树 的 直 径 最 小 , 最 小 是 多 少 ? 如果让新树的直径最小,最小是多少? ,
答 案 是 m a x ( ⌈ d i m a [ x ] / 2 ⌉ 答案是max(\lceil dima[x]/2 \rceil max(dima[x]/2 + ⌈ d i m a [ y ] / 2 ⌉ \lceil dima[y]/2 \rceil dima[y]/2 + 1 +1 +1, m a x ( d i m a [ x ] , d i m a [ y ] ) max(dima[x],dima[y]) max(dima[x],dima[y]))
为 什 么 是 这 个 ? 为什么是这个? ?
因 为 选 择 相 连 的 两 个 点 应 该 在 两 颗 树 的 直 径 上 , 且 应 该 在 直 径 的 中 间 因为选择相连的两个点应该在两颗树的直径上,且应该在直径的中间
所 以 是 两 颗 树 的 直 径 向 上 取 整 之 和 再 加 上 连 边 , 其 次 为 什 么 还 要 和 取 两 个 直 径 取 m a x 所以是两颗树的直径向上取整之和再加上连边,其次为什么还要和取两个直径取max max
可 以 想 象 当 一 颗 直 径 很 大 的 树 连 接 一 个 点 时 , 答 案 应 该 还 是 这 颗 大 树 的 直 径 可以想象当一颗直径很大的树连接一个点时,答案应该还是这颗大树的直径

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 3e5+10;
int n,m,q,fa[N],len;
inline int read() {
	char ch=getchar();
	int x=0,cf=1;
	while(ch<'0'||ch>'9') {
		if(ch=='-') cf=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return x*cf;
}
struct Node{
	int to,nex;
}e[N<<1];
int head[N],idx,dima[N];
void add_edge(int u,int v){
	e[idx].to = v;
	e[idx].nex = head[u];
	head[u] = idx++;
}
int find(int x){
	return fa[x] == x?x:fa[x] = find(fa[x]);
}
int dfs(int u,int father){
	int dist = 0;//表示以u往下走的最大长度
	int d1 = 0,d2 = 0;//最大长度,次大长度
	for(int i = head[u];~i;i=e[i].nex){
		int v = e[i].to;
		if(v == father) continue;
		int d = dfs(v,u)+1;
		dist = max(dist,d);
		if(d >= d1) d2 = d1,d1 = d;
		else if(d > d2) d2 = d;
	} 
	len = max(len,d1+d2);
	return dist;
}
void cal(int root){
	len = 0;
	dfs(root,0);
	dima[root] = len;
}
int main(){
	memset(head,-1,sizeof(head));
	n = read(),m = read(),q = read();
	for(int i = 1;i <= n;i++) fa[i] = i;
	for(int i = 0,u,v;i < m;i++){
		u = read(),v = read();
		fa[find(u)] = find(v);
		add_edge(u,v),add_edge(v,u);
	}
	for(int i = 1;i <= n;i++){
		if(fa[i] != i) continue;
		cal(i);//传入根计算该树的直径
	}
	while(q--){
		int op = read();
		if(op == 1){
			int x = read();
			printf("%d\n",dima[find(x)]);
		}else{
			int x = read(),y = read();
			x = find(x),y = find(y);
			if(x==y) continue;
			int ans = max(((dima[x]+1)>>1) + ((dima[y]+1)>>1) + 1,max(dima[x],dima[y]));
			fa[find(x)] = y,dima[find(x)] = ans;
		}
	} 
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值