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

来源:互联网 发布:qq自动群发软件 编辑:程序博客网 时间:2024/05/16 17:37

关键词:树结构->线性结构、倒序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...)


0 0
原创粉丝点击