P3258
题意:
给
你
一
颗
树
,
和
一
组
路
径
,
每
次
从
前
一
个
点
走
到
后
一
个
点
,
给你一颗树,和一组路径,每次从前一个点走到后一个点,
给你一颗树,和一组路径,每次从前一个点走到后一个点,
走
过
的
点
都
加
一
,
问
最
后
所
有
点
的
值
走过的点都加一,问最后所有点的值
走过的点都加一,问最后所有点的值
思路:
直
接
树
剖
硬
刚
,
注
意
一
下
上
一
次
的
终
点
是
下
一
次
的
起
点
,
不
能
加
了
两
次
直接树剖硬刚,注意一下上一次的终点是下一次的起点,不能加了两次
直接树剖硬刚,注意一下上一次的终点是下一次的起点,不能加了两次
只
需
要
记
录
这
个
点
作
为
过
几
次
终
点
就
可
以
了
,
每
次
u
到
v
的
路
径
直
接
只需要记录这个点作为过几次终点就可以了,每次 u 到 v的路径直接
只需要记录这个点作为过几次终点就可以了,每次u到v的路径直接
全
部
加
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个点使得
0号节点必选,且0号节点价值和花费都为0,让你再选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]=Pi−mid∗Si,然后做树形背包,判断
d
p
[
0
]
[
m
+
1
]
是
否
大
于
等
于
0
dp[0][m+1] 是否大于等于0
dp[0][m+1]是否大于等于0
这
题
卡
得
慌
,
T
了
两
个
点
,
开
了
O
2
才
过
这题卡得慌,T了两个点,开了O_2才过
这题卡得慌,T了两个点,开了O2才过
#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;
}