P4145
题意:
给
一
个
长
度
为
n
的
数
列
,
有
两
个
操
作
:
给一个长度为n的数列,有两个操作:
给一个长度为n的数列,有两个操作:
①
将
[
L
,
R
]
内
的
数
都
取
平
方
根
(
向
下
取
整
)
①将[L,R]内的数都取平方根(向下取整)
①将[L,R]内的数都取平方根(向下取整)
②
求
[
L
,
R
]
的
区
间
和
②求[L,R]的区间和
②求[L,R]的区间和
思路:
暴
力
单
点
修
改
可
过
暴力单点修改可过
暴力单点修改可过
可
知
若
对
1
取
平
方
根
也
是
1
,
可
以
把
这
种
操
作
省
略
可知若对1取平方根也是1,可以把这种操作省略
可知若对1取平方根也是1,可以把这种操作省略
当
要
修
改
这
个
区
间
时
,
若
该
区
间
最
大
值
小
于
等
于
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条边,使得这棵树边权之和最大
要求你把这颗以1为根的树砍剩下m条边,使得这棵树边权之和最大
思路:
和
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组操作
给一个有n个点,m条边的森林,有q组操作
有
两
种
类
型
的
操
作
:
有两种类型的操作:
有两种类型的操作:
①
①
①
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所在树
:若x和y本来在一棵树忽略这次操作,否则将x所在树和y所在树
用
一
条
边
连
成
一
颗
新
树
,
并
要
求
这
颗
新
树
的
直
径
在
所
有
方
案
中
最
小
用一条边连成一颗新树,并要求这颗新树的直径在所有方案中最小
用一条边连成一颗新树,并要求这颗新树的直径在所有方案中最小
思路:
首
先
对
于
一
棵
树
的
用
并
查
集
连
起
来
,
并
且
求
出
每
颗
树
的
直
径
;
重
点
在
于
第
二
个
操
作
首先对于一棵树的用并查集连起来,并且求出每颗树的直径;重点在于第二个操作
首先对于一棵树的用并查集连起来,并且求出每颗树的直径;重点在于第二个操作
如
果
让
新
树
的
直
径
最
小
,
最
小
是
多
少
?
如果让新树的直径最小,最小是多少?
如果让新树的直径最小,最小是多少?
答
案
是
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;
}