题解 [51nod1673] 树有几多愁

题面

解析

这题思路挺秒啊.

本麻瓜终于找了道好题了(还成功把ztlztl大仙拖下水了)

看到叶子节点数<=20就应该是状压啊.

然而DP要怎么写啊?

首先,考虑到编号肯定是从下往上一次增大的,

另外,对于没有分支的一条链,它的编号应该是连续的.

并且一种类似于贪心的想法就是一个点\(u\)被编号时它的子树一定被编号完了.

所以这也像是一个类似于拓扑序的东西.

先建一棵虚树(因为叶子节点只有20有很多没用的点),边权设为这条链上不在虚树上的点数.

设状态\(i\)表示状压后集合\(i\)中的点的编号已经确定了.

那么我们可以把所有已经编号了的点数\(cnt\)求出来,

然后枚举没在点集中的叶子节点,它的编号就应该是\(cnt+1\),再更新答案就行了.

因为取模后无法比较大小所以我们可以另外开一个\(double\)数组来比较.

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define int long long
#define fre(x) freopen(x".in","r",stdin),freopen(x".out","w",stdout)
using namespace std;

inline int read(){
    int sum=0,f=1;char ch=getchar();
    while(ch>'9' || ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0' && ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
    return f*sum;
}

const int N=100005;
const int M=(1<<20)+5;
const int Mod=1000000007;
struct edge{int to,next,w;}e[N<<1];
struct node{int size,dep,fa,son,top,dfn,is;}a[N];
int n;
int head[N],cnt=0,tp;
int q[N],sta[N],tot,top;
int f[M],s[N];double dp[M];
int que[N],qq[N],tt;

inline void add(int x,int y,int w){
    e[++cnt]=(edge){head[x],y,w-1};head[x]=cnt;
}

inline void dfs(int x,int fa){
    a[x].dep++;a[x].fa=fa;
    a[x].size=a[x].is=1;a[x].dfn=++tp;
    for(int i=head[x];i;i=e[i].to){
        int k=e[i].next;if(k==fa) continue;
        a[k].dep=a[x].dep+e[i].w;
        dfs(k,x);a[x].size+=e[i].w+a[k].size;
        if(a[k].size>a[a[x].son].size) a[x].son=k;
        a[x].is=0;
    }
}

inline void dfs2(int x,int top){
    a[x].top=top;
    if(a[x].son) dfs2(a[x].son,top);
    for(int i=head[x];i;i=e[i].to){
        int k=e[i].next;
        if(k==a[x].son||k==a[x].fa) continue;
        dfs2(k,k);
    }
}

inline int lca(int x,int y){
    while(a[x].top!=a[y].top){
        if(a[a[x].top].dep<a[a[y].top].dep) swap(x,y);
        x=a[a[x].top].fa;
    }
    if(a[x].dep>a[y].dep) swap(x,y);
    return x;
}//树剖求lca

inline bool cmp(int x,int y){return a[x].dfn<a[y].dfn;}

signed main(){
    n=read();
    for(int i=1;i<n;i++){int x=read(),y=read();add(x,y,1);add(y,x,1);}
    dfs(1,0);dfs2(1,1);
    for(int i=1;i<=n;i++) if(a[i].is) q[++tot]=i;
    sort(q+1,q+tot+1,cmp);if(q[1]!=1) sta[++top]=1;
    memset(head,0,sizeof(head));cnt=0;
    //*******
    for(int i=1;i<=tot;i++){
        if(!top){sta[++top]=q[i];continue;}
        int p=lca(sta[top],q[i]);
        while(top>1&&a[sta[top-1]].dep>=a[p].dep)
        {add(sta[top-1],sta[top],a[sta[top]].dep-a[sta[top-1]].dep);top--;}
        if(sta[top]!=p) add(p,sta[top],a[sta[top]].dep-a[p].dep),sta[top]=p;
        sta[++top]=q[i];
    }
    while(top>1) add(sta[top-1],sta[top],a[sta[top]].dep-a[sta[top-1]].dep),top--;
    //*******建虚树
    a[1].dep=0;dfs(1,0);int lim=1<<tot;
    f[0]=dp[0]=1;
    for(int i=0;i<lim;i++){
        for(int j=1;j<=tot;j++) if((i&(1<<(j-1)))) s[q[j]]=1;
        int ret=0,l=1,r=0;tt=0;
        for(int j=1;j<=tot;j++) if(s[q[j]]) que[++r]=q[j];
        while(l<=r){
            int x=que[l];l++;qq[++tt]=x;
            ret+=a[x].dep-a[a[x].fa].dep;
            if(!a[x].fa) continue;qq[++tt]=a[x].fa;
            s[a[x].fa]+=s[x]+a[x].dep-a[a[x].fa].dep-1;
            if(s[a[x].fa]==a[a[x].fa].size-1) s[a[x].fa]++,que[++r]=a[x].fa;
        }//像拓扑序一样统计数量
        for(int i=1;i<=tt;i++) s[qq[i]]=0;//清空s数组(之前用memsetT得一脸懵逼)
        ret++;
        for(int j=1;j<=tot;j++){
            if((i&(1<<(j-1)))) continue;
            int k=i|(1<<(j-1));
            if(dp[k]<dp[i]*ret) f[k]=f[i]*ret%Mod,dp[k]=dp[i]*ret;
        }
    }
    printf("%lld\n",f[lim-1]);
    return 0;
}

原文地址:https://www.cnblogs.com/zsq259/p/11422375.html

时间: 2024-10-11 13:59:38

题解 [51nod1673] 树有几多愁的相关文章

[51nod1673]树有几多愁

lyk有一棵树,它想给这棵树重标号. 重标号后,这棵树的所有叶子节点的值为它到根的路径上的编号最小的点的编号. 这棵树的烦恼值为所有叶子节点的值的乘积. lyk想让这棵树的烦恼值最大,你只需输出最大烦恼值对1e9+7取模后的值就可以了. 注意一开始1号节点为根,重标号后这个节点仍然为根. update:数据保证叶子节点个数<=20. Input 第一行一个数n(1<=n<=100000). 接下来n-1行,每行两个数ai,bi(1<=ai,bi<=n),表示存在一条边连接这两

51nod 1673 树有几多愁

lyk有一棵树,它想给这棵树重标号. 重标号后,这棵树的所有叶子节点的值为它到根的路径上的编号最小的点的编号. 这棵树的烦恼值为所有叶子节点的值的乘积. lyk想让这棵树的烦恼值最大,你只需输出最大烦恼值对1e9+7取模后的值就可以了. 注意一开始1号节点为根,重标号后这个节点仍然为根. update:数据保证叶子节点个数<=20. 例如样例中,将1,2,3,4,5重标号为4,3,1,5,2,此时原来编号为4,5的两个叶子节点的值为3与1,这棵树的烦恼值为3.不存在其它更优解. Input 第一

POJ2182题解——线段树

POJ2182题解——线段树 2019-12-20 by juruoOIer 1.线段树简介(来源:百度百科) 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN).而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩. 2.线段树实际应用 上面的都是些基本的线段树结构,但只有这些并不能做什么,就好比一个程序有

POJ2352题解(树状数组)

POJ2352题解(树状数组) 2019-12-29 Powered by Gauss 1.题目传送门:POJ2352 2.题目大意: 这是一道非常经典的树状数组的模板题. 题目大意是说,给出N颗星星,每个星星都有一个二维坐标,要求出位于每颗星星左下方的星星的数量. 3.算法思路: 这道题被给出之后立刻想到暴力法,让我们来计算一下: 根据题目,N<=15000,暴力法需要双层循环,也就是150002=225,000,000,超出了一秒,所以不得不寻求更快更好的算法思想. 当我们需要求查询,求和的

题解 滑稽树前做游戏,滑稽树后做交易 trade

题目名称我就不吐槽了... 题目描述 滑稽树前做游戏,滑稽树后做交易 (trade.cpp,3000ms,1024MB) 题目描述 滑稽果被排成一列,poison 的 lsr 要求每个顾客只能买一段连续的区间. sxd 来这里买滑稽果,他对每个滑稽果都有一个喜爱程度 Ai 是一个整数,-100≤Ai≤100, 并保证∑Ai <=2147483647,最终的满意度为所有他买到的滑稽果的喜欢程度之和,如果和 为正(不管是正多少,只要大于 0 即可),则他满意了. 现在 sxd 想知道在他满意的条件下

理想乡题解 (线段树优化dp)

题面 思路概述 首先,不难想到本题可以用动态规划来解,这里就省略是如何想到动态规划的了. 转移方程 f[i]=min(f[j]+1)(max(i-m,0)<=j<i 且j符合士兵限定) 注意要用 max(i-m,0)以防止越界 我们先用两个数组sl,sa分别统计1~i个士兵中有多少个Lencer和Archer 然后在max(i-m,0)中寻找符合条件的j (1).两种士兵相差不超过k. 这个好说abs((sl[i]-sl[j])-(sa[i]-sa[j]))<=k 不要忘了第二种情况!!

POJ2299题解(树状数组)

POJ2299题解 2019-12-28 Powered by Gauss 1.题目传送门:POJ2299 2.理解题意: 做题的时候,第一步也是最重要的就是理解题意.记住这句话. POJ2299是一道标准的树状数组模板题.题意大致如下: 快速排序是一种非常优秀的排序方式,其精华在于循环比较和swap函数的应用. 先给你一个数N,表示这组数据(没错,这题是多组数据)数的个数,之后的N行,每行一个数Ai 请你输出在快速排序过程中经历的swap函数的次数. 3.算法思路: 树状数组是一种非常高效的数

[bzoj3932][CQOI2015]任务查询系统-题解[主席树][权值线段树]

Description 最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分.超级计算机中的 任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第Ei秒后结束(第Si秒和Ei秒任务也在运行 ),其优先级为Pi.同一时间可能有多个任务同时执行,它们的优先级可能相同,也可能不同.调度系统会经常向 查询系统询问,第Xi秒正在运行的任务中,优先级最小的Ki个任务(即将任务按照优先级从小到大排序后取前Ki个 )的优先级之和是多少.特别的,如

[题解]线段树专题测试2017.1.21

很单纯的一道线段树题.稍微改一下pushDown()就行了. Code(线段树模板竟然没超100行) 1 #include<iostream> 2 #include<sstream> 3 #include<cstdio> 4 #include<cmath> 5 #include<cstdlib> 6 #include<cstring> 7 #include<cctype> 8 #include<queue> 9