洛谷每日一練5.11--P4145+P2015+P2195(圖+線段樹+DP)

P4145

題意:

n給一個長度爲n的數列,有兩個操作:
[L,R]()①將[L,R]內的數都取平方根(向下取整)
[L,R]②求[L,R]的區間和

思路:

暴力單點修改可過
11可知若對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爲根的樹給你,每條樹邊都有一個權重
1m,使要求你把這顆以1爲根的樹砍剩下m條邊,使得這棵樹邊權之和最大

思路:

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

題意:

nmq給一個有n個點,m條邊的森林,有q組操作
有兩種類型的操作:
11 xx x:詢問x所在樹的直徑
22 xx yy xyxy:若x和y本來在一棵樹忽略這次操作,否則將x所在樹和y所在樹
用一條邊連成一顆新樹,並要求這顆新樹的直徑在所有方案中最小

思路:

首先對於一棵樹的用並查集連起來,並且求出每顆樹的直徑;重點在於第二個操作
,如果讓新樹的直徑最小,最小是多少?
max(dima[x]/2答案是max(\lceil dima[x]/2 \rceil + dima[y]/2\lceil dima[y]/2 \rceil+1+1,max(dima[x],dima[y])max(dima[x],dima[y]))
?爲什麼是這個?
因爲選擇相連的兩個點應該在兩顆樹的直徑上,且應該在直徑的中間
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;
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章