《浅谈数据的合理组织》笔记

关键词:树结构->线性结构、倒序dp、背包问题初始化、第二类线段树

例一:(vjios1642) 树中每个点有一个权值,求不超过m点且满足性质P的最大权值点集,性质P:点集中任一点的父节点一定也在点集中。

做法:将树结构线性化。

dfs性质:dfs遍历后子树在一个以根为起始点的连续区间内

前根遍历,得到数组line[],记录元素i的子树区间结束点(即子树元素个数)sum[i],设dp[i][j]:数组中元素i-n中选择j个点的最大权值,则dp[i][j]=max{ dp[i+1][j-1]+cost[line[i]],dp[i+sum[line[i]]][j] } 

为什么要倒序dp,原因:决策是选择i子树中若干元素/不选i所在子树中任何元素,决策的结果是数组下标变大,需要大下标的dp值作为依靠,因此遍历需要向前,故而倒序dp

最后注意:背包问题初始化:不超过m件物品,初始化为0;装满m件物品,只有dp[i][0]初始化为0,其他初始化为-INF。


#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
#define ll long long
#define sf scanf
#define pf printf
#define maxn 10000
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
const ll mod=1000000007;
using namespace std;

int n,m;
struct Edge{
    int to,next;
}edge[maxn];
int head[maxn],tot;
int cost[maxn];
int line[maxn],cnt,sum[maxn];
int dp[maxn][maxn];

void add(int u,int v){
    edge[tot].to=v,edge[tot].next=head[u],head[u]=tot++;
}

void tree_line(int u){
    line[cnt++]=u,sum[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        tree_line(v);
        sum[u]+=sum[v];
    }
}

int main(){
    //freopen("a.txt","r",stdin);
    scanf("%d%d",&n,&m);
    mem(head,-1),tot=0,cnt=0,mem(sum,0),mem(cost,0);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            dp[i][j]=0;//初始化---不装满:初始化为0
    for(int i=1;i<=n;i++){
        int s,son;
        scanf("%d%d",&cost[i],&s);
        for(int j=1;j<=s;j++){
            scanf("%d",&son);
            add(i,son);
        }
    }
    tree_line(1);
    for(int i=cnt-1;i>=0;i--){//dp[i][j]=max{dp[i+sum[line[i]]][j],dp[i+1][j-1]+cost[list[i]]}
        for(int j=1;j<=m;j++){
            dp[i][j]=max(dp[i+sum[line[i]]][j],dp[i+1][j-1]+cost[line[i]]);
        }
    }
    int ans=dp[0][m];
    printf("%d\n",ans);
    return 0;
}


例二:有根树,对每个节点,求:1..它到根节点路径上比它权值大的节点数目 2.其子树上比它权值大的节点数目

1.从根节点开始遍历,第一次经过节点时加入该节点,遍历完成后去除该节点,即可得到根节点到达每一节点的路径。则题目转化为求第i个元素之前比i大的元素个数,于是可以使用树状数组进行统计。

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
#define ll long long
#define sf scanf
#define pf printf
#define maxn 10000
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
const ll mod=1000000007;
using namespace std;

int n,cost[maxn],cost1[maxn];
int c[maxn],ans[maxn];
vector<int> g[maxn];

void seperate(int h[],int h1[]){//数据过大时,可以先离散化
    int h0[maxn];
    for(int i=1;i<=n;i++) h0[i]=h[i];
    sort(h0+1,h0+n+1);
    for(int i=1;i<=n;i++) h1[i]=lower_bound(h0+1,h0+n+1,h[i])-h0;
}

void build_map(){
    for(int i=1;i<=n;i++) g[i].clear();
    for(int i=1;i<n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        g[a].push_back(b);
    }
}

int sum(int i){
    int s=0;
    for(;i>0;i-=lowbit(i)) s+=c[i];
    return s;
}

void update(int i,int x){
    for(;i<=maxn;i+=lowbit(i)) c[i]+=x;
}

void dfs(int u){
    ans[u]=sum(maxn)-sum(cost1[u]);//根节点到u的路径上比u大的元素个数
    update(cost1[u],1);
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        dfs(v);
    }
    update(u,-1);
}

int main(){
    //freopen("a.txt","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&cost[i]);
    seperate(cost,cost1);
    build_map();
    mem(c,0);
    dfs(1);
    for(int i=1;i<=n;i++) printf("%d ",ans[i]);
    printf("\n");
}

2.统计子树中比该子树根节点大的元素的个数

首先,和例一一样,先把树结构转化为数组。问题转化为:

一个静态序列a[1..n],询问n组子区间中比所在子区间起始位置元素大的元素个数

做法:第二种统计区间元素比x大/小的元素个数方法。1.将所有数从大到小排序,记录在原数组中的下标位置(结构体存储) 2.将这些数从大到小依次插入到原数组中的相应位置,标记为1,插入前统计以该点起始的子树区间的和---即为该子树中比子树根节点元素大的个数。

第一种线段树方法是以值为底建立线段树,若值较大时,需要对相应值进行离散化;第二种仍以下标为底建立线段树,只不过需要先排序,按从大到小的顺序插入。可以解决"任意区间的统计较大元素的个数"问题

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
#define ll long long
#define sf scanf
#define pf printf
#define maxn 10000
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
const ll mod=1000000007;
using namespace std;

int n,cost[maxn],cost1[maxn];
int sum[maxn],line[maxn],cnt;
int c[maxn],ans[maxn];
vector<int> g[maxn];
struct node{
    int id,cost;
    bool operator <(const node &rhs)const{
        return cost>rhs.cost;
    }
}vertex[maxn];

void seperate(int h[],int h1[]){
    int h0[maxn];
    for(int i=1;i<=n;i++) h0[i]=h[i];
    sort(h0+1,h0+n+1);
    for(int i=1;i<=n;i++) h1[i]=lower_bound(h0+1,h0+n+1,h[i])-h0;
}

void build_map(){
    for(int i=1;i<=n;i++) g[i].clear();
    for(int i=1;i<n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        g[a].push_back(b);
    }
}

void tree_line(int u){
    sum[u]=1,line[cnt]=u,num[u]=cnt++;//cnt:0...n-1
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        tree_line(v);
        sum[u]+=sum[v];
    }
}

int cal(int i){
    int s=0;
    for(;i>0;i-=lowbit(i)) s+=c[i];
    return s;
}

void update(int i,int x){
    for(;i<=maxn;i+=lowbit(i)) c[i]+=x;
}

int main(){
    //freopen("a.txt","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&cost[i]);
    seperate(cost,cost1);
    build_map();
    cnt=1,mem(c,0),mem(num,0);
    tree_line(1);
    //转化为解决line数组中任意区间的较大元素统计问题
    for(int i=1;i<=n;i++) { vertex[i].cost=cost1[line[i]],vertex[i].id=i; }//id是在line数组中的编号
    sort(vertex+1,vertex+n+1);
    for(int i=1;i<=n;i++){
        ans[line[vertex[i].id]]=cal(vertex[i].id+sum[line[vertex[i].id]]-1)-cal(vertex[i].id);
        update(vertex[i].id,1);
    }
    for(int i=1;i<=n;i++) printf("%d ",ans[i]);
    printf("\n");
}

例三:航线规划

不断添边并询问任意两点间桥的数量

思路:1.缩点,建立edge_bcc图 2.添边后,缩点,更新图和深度(如何实现???代码能力不足Orz...)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值