洛谷每日一练5.12--P3258+P4322(线段树+树形背包)

P3258

题意:

给 你 一 颗 树 , 和 一 组 路 径 , 每 次 从 前 一 个 点 走 到 后 一 个 点 , 给你一颗树,和一组路径,每次从前一个点走到后一个点,
走 过 的 点 都 加 一 , 问 最 后 所 有 点 的 值 走过的点都加一,问最后所有点的值

思路:

直 接 树 剖 硬 刚 , 注 意 一 下 上 一 次 的 终 点 是 下 一 次 的 起 点 , 不 能 加 了 两 次 直接树剖硬刚,注意一下上一次的终点是下一次的起点,不能加了两次
只 需 要 记 录 这 个 点 作 为 过 几 次 终 点 就 可 以 了 , 每 次 u 到 v 的 路 径 直 接 只需要记录这个点作为过几次终点就可以了,每次 u 到 v的路径直接 uv
全 部 加 1 , 算 答 案 时 减 去 这 个 点 作 为 终 点 的 次 数 全部加1,算答案时减去这个点作为终点的次数 1

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 3e5+10;
int n;
int head[N],idx;
struct Edge{
	int to,nex;
}e[N<<1];
void add_edge(int u,int v){
	e[idx].to = v;
	e[idx].nex = head[u];
	head[u] = idx++;
}
int a[N],dep[N],fa[N];//路径,深度,父亲 
int son[N],sz[N],id[N],dfn[N],tt; //重儿子,子树大小,dfn是i的编号,dfn 
int top[N];//链头 
int st[N];
inline int read()
{
    register int x = 0 , ch = getchar();
    while( !isdigit( ch ) ) ch = getchar();
    while( isdigit( ch ) ) x = x * 10 + ch - '0' , ch = getchar();
    return x;
}
void print(int x)
{
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    if(x>9)
        print(x/10);
    putchar(x%10+'0');
}
//线段树部分 
struct Node{
	int l,r,sum,lazy;//区间左键,区间右键,sum,lazy标记 
}tree[N<<2];
void build(int k,int l,int r){
	tree[k].l = l;
	tree[k].r = r;
	if(tree[k].l == tree[k].r){
		tree[k].sum = 0;
		return;
	} 
	int mid = l + r>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid + 1,r);
	tree[k].sum = (tree[k<<1].sum + tree[k<<1|1].sum); 
}
//下放lazy 
void pushdown(int k){
	tree[k<<1].lazy += tree[k].lazy;
	tree[k<<1|1].lazy += tree[k].lazy;
	tree[k<<1].sum += (tree[k<<1].r - tree[k<<1].l + 1)*tree[k].lazy;
	tree[k<<1|1].sum += (tree[k<<1|1].r - tree[k<<1|1].l + 1)*tree[k].lazy;
	tree[k].lazy = 0;
} 
void update(int k,int L,int R,int val){
	if(tree[k].l >= L&&tree[k].r <= R){
		tree[k].sum += (tree[k].r - tree[k].l + 1)*val;
		tree[k].lazy+=val;
		return; 
	}
	if(tree[k].lazy) pushdown(k);
	int mid = tree[k].l + tree[k].r>>1;
	if(L <= mid) update(k<<1,L,R,val);
	if(R > mid) update(k<<1|1,L,R,val);
	tree[k].sum = (tree[k<<1].sum + tree[k<<1|1].sum);
}
int query(int k,int L,int R){
	if(L <= tree[k].l&&R >= tree[k].r) return tree[k].sum;
	if(tree[k].lazy) pushdown(k);
	int mid = tree[k].l + tree[k].r>>1;
	int ans = 0;
	if(L <= mid) ans+=query(k<<1,L,R);
	if(R > mid) ans+=query(k<<1|1,L,R);
	return ans;
}
//Segment Tree end 
 
void dfs1(int u){
	sz[u] = 1;
	for(int i = head[u];~i;i = e[i].nex){
		int v = e[i].to;
		if(v==fa[u]) continue;
		dep[v] = dep[u]+1;fa[v] = u;
		dfs1(v);
		sz[u]+=sz[v];
		if(sz[v] > sz[son[u]]){
			son[u] = v;
		}
	}
}  
//u和该重链的链头 
void dfs2(int u,int x){
	dfn[u] = ++tt;//时间戳
	id[tt] = u;
	top[u] = x;
	//没有重儿子即叶子结点,return 
	if(!son[u]) return;
	dfs2(son[u],x);
	for(int i = head[u];~i;i = e[i].nex){
		int v = e[i].to;
		if(v == fa[u]||v == son[u]) continue;
		dfs2(v,v);//走轻边即去到另一条重链 
	} 
}
int qPath(int u,int v){
	int ans = 0;
	//跳到同一条重链
	while(top[u]!=top[v]){
		//默认拿u去跳 
		if(dep[top[u]] < dep[top[v]]) swap(u,v);
		ans = (ans+query(1,dfn[top[u]],dfn[u]));
		u = fa[top[u]];//跳到top[u]的父亲就可以跳到另外一条重链 
	} 
	//已经处于同一条重链
	if(dep[u] < dep[v]) swap(u,v);
	ans = (ans+query(1,dfn[v],dfn[u]));
	return ans; 
}
//与qPath同理 
int updatePath(int u,int v,int dx){
	//跳到同一条重链
	while(top[u]!=top[v]){
		//默认拿u去跳 
		if(dep[top[u]] < dep[top[v]]) swap(u,v);
		update(1,dfn[top[u]],dfn[u],dx);
		u = fa[top[u]];//跳到top[u]的父亲就可以跳到另外一条重链 
	} 
	//已经处于同一条重链
	if(dep[u] > dep[v]) swap(u,v);
	update(1,dfn[u],dfn[v],dx);
}
int main(){
	build(1,1,N-5);
	for(int i = 1;i < N;++i) head[i] = -1;
	n = read();
	for(int i = 1;i <= n;++i) a[i] = read();
	for(int i = 1,u,v;i < n;++i){
		u = read(),v = read();
		add_edge(u,v),add_edge(v,u);
	}
	dfs1(1);
	dfs2(1,1);
	for(int i = 1;i < n;i++){
		updatePath(a[i],a[i+1],1);
		st[a[i+1]] ++;
	}
	for(int i = 1;i <= n;i++){
		print(query(1,dfn[i],dfn[i])-st[i]);
		putchar('\n');
	}
	return 0;
}

P4322

题意:

給 以 0 号 节 点 为 根 的 一 颗 树 给 你 , 每 个 节 点 有 其 价 值 和 花 费 , 給以0号节点为根的一颗树给你,每个节点有其价值和花费, 0
0 号 节 点 必 选 , 且 0 号 节 点 价 值 和 花 费 都 为 0 , 让 你 再 选 m 个 点 使 得 0号节点必选,且0号节点价值和花费都为0,让你再选m个点使得 000,m使
收 益 与 花 费 的 比 最 大 。 另 外 , 选 人 的 条 件 是 若 选 择 了 一 个 点 收益与花费的比最大。另外,选人的条件是若选择了一个点
就 必 须 要 选 该 节 点 的 父 节 点 就必须要选该节点的父节点

思路:

首 先 对 于 这 类 型 令 ∑ ( P i ) ∑ ( S i ) 最 大 , 这 是 01 分 数 规 划 , 二 分 找 答 案 , 让 首先对于这类型 令 \frac{\sum(P_i)}{\sum{(S_i)}} 最大,这是01分数规划,二分找答案,让 (Si)(Pi)01
v a l [ i ] = P i − m i d ∗ S i , 然 后 做 树 形 背 包 , 判 断 val[i] = P_i - mid*S_i,然后做树形背包,判断 val[i]=PimidSi
d p [ 0 ] [ m + 1 ] 是 否 大 于 等 于 0 dp[0][m+1] 是否大于等于0 dp[0][m+1]0
这 题 卡 得 慌 , T 了 两 个 点 , 开 了 O 2 才 过 这题卡得慌,T了两个点,开了O_2才过 TO2

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
# define rg register
using namespace std;
const double eps = 1e-5;
const int inf = 0x3f3f3f3f;
const int N = 2510;
int head[N],idx;
struct Node{
	int to,nex;
}e[N<<1];
inline int read()
{
    register int x = 0 , ch = getchar();
    while( !isdigit( ch ) ) ch = getchar();
    while( isdigit( ch ) ) x = x * 10 + ch - '0' , ch = getchar();
    return x;
}
void add_edge(int u,int v){
	e[idx].to = v;
	e[idx].nex = head[u];
	head[u] = idx++;
}
double p[N],s[N];
double val[N];
double dp[N][N];
int n,m;
int sz[N];
void dfs(int u,int fa){
	dp[u][1] = val[u];
	sz[u] = 1;
	for(rg int i = head[u];~i;i = e[i].nex){
		int v = e[i].to;
		if(v == fa) continue;
		dfs(v,u);
		sz[u] += sz[v];
		//倒序枚举容量避免重复计算一个子节点的值
		for(rg int j = min(m,sz[u]);j > 0;j--){
			//枚举子节点选取节点的数量
			for(rg int k = 0;k <= min(sz[v],j-1);k++){
				dp[u][j] = max(dp[u][j],dp[u][j-k]+dp[v][k]);
			} 
		} 
	}
} 
bool check(double x){
	val[0] = 0;
	for(rg int i = 1;i <= n;i++) val[i] = p[i] - 1.0*x*s[i];
	memset(dp,-inf,sizeof(dp));
	dfs(0,-1);
	return dp[0][m] >= 0;
}
int main(){
	memset(head,-1,sizeof(head));
	scanf("%d%d",&m,&n);
	for(int i = 1,v;i <= n;i++){
		s[i] = read(),p[i] = read(),v = read();
		add_edge(i,v),add_edge(v,i);
	}
	m++;
	double l = 0,r = 10000;
	while(r - l > eps){
		double mid = ( l + r )/ 2.0;
		if(check(mid)){
			l = mid;
		}else r = mid;
	}
	printf("%.3lf\n",l);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值