刷题树形dpZJOI 骑士
Posted xwww666666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了刷题树形dpZJOI 骑士相关的知识,希望对你有一定的参考价值。
国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。
为了描述战斗力,我们将骑士按照1至N编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。
因为一个骑士有且只有一个最讨厌的人(一条出边)
而且这个骑士不会讨厌自己
即该图中是没有自环的
所以 考虑这个有向图
我们把x所讨厌的人y设为x的父亲节点
这样考虑每一个人都有且只有一条出边
所以对一个"联通块"
只有根节点有机会形成环(寻找到题目中图形的具体形状)
所以我们又解决了一个问题:
每个联通块内有且只有一个简单环
这样 我们考虑把每个联通块的环上删一条边
这样它必然构成树
然后要注意
删掉的边所连接的两点x,y
是不能同时选的
所以我们分别强制x,y其中一个点不选
对新树跑DP
显然相邻的点是不能选的
就是O(2n)
两个细节:
(1)vector上跑树超时 647ms -> 1.47s
(2)int会爆
#include<cstdio> #include<cstdlib> #include<vector> #define ll long long using namespace std; int n; const int N=1e6+3; int tot,head[N]; struct node { int v,nx; }e[N]; void add(int u,int v) {e[++tot].v =v,e[tot].nx =head[u],head[u]=tot;} int sz[N]; int fa[N],d[N]; bool vis[N]; ll f[N][2]; ll ans;int root; void dfs(int rt) { vis[rt]=true; f[rt][0]=0,f[rt][1]=d[rt]; for(int i=head[rt];i;i=e[i].nx ) { int v=e[i].v ; if(v==root) continue; dfs(v); f[rt][0]+=max(f[v][0],f[v][1]); f[rt][1]+=f[v][0]; } } void find_circle(int rt) { int r1,r2; vis[rt]=true; while(!vis[fa[rt]]) { rt=fa[rt]; vis[rt]=true; } r1=rt,r2=fa[rt]; root=r1; dfs(r1); ll t=f[r1][0]; root=r2; dfs(r2); t=max(t,f[r2][0]); ans+=t; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d",&d[i],&fa[i]), add(fa[i],i); for(int i=1;i<=n;i++) if(!vis[i]) find_circle(i); printf("%lld ",ans); return 0; }
然后我觉得复杂度太高,就进行了记忆化,
因为只有一个环路,两次dp路径差不多,所以没有影响的另外一支,不用重复求
647->547ms,考试还是稳妥一点好
#include<cstdio> #include<cstdlib> #include<vector> #define ll long long using namespace std; int n; const int N=1e6+3; int tot,head[N]; struct node { int v,nx; }e[N]; void add(int u,int v) {e[++tot].v =v,e[tot].nx =head[u],head[u]=tot;} int sz[N]; int fa[N],d[N]; bool vis[N]; int in[N]; ll f[N][2]; ll ans;int root; void dfs(int rt) { if(in[rt]==2 ) return ; in[rt]=2; vis[rt]=true; f[rt][0]=0,f[rt][1]=d[rt]; for(int i=head[rt];i;i=e[i].nx ) { int v=e[i].v ; if(v==root) { in[rt]=1; continue; } dfs(v); if(in[v]==1) in[rt]=1; f[rt][0]+=max(f[v][0],f[v][1]); f[rt][1]+=f[v][0]; } } void find_circle(int rt) { int r1,r2; vis[rt]=true; while(!vis[fa[rt]]) { rt=fa[rt]; vis[rt]=true; } r1=rt,r2=fa[rt]; root=r1; dfs(r1); ll t=f[r1][0]; root=r2; dfs(r2); t=max(t,f[r2][0]); ans+=t; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d",&d[i],&fa[i]), add(fa[i],i); for(int i=1;i<=n;i++) if(!vis[i]) find_circle(i); printf("%lld ",ans); return 0; }
以上是关于刷题树形dpZJOI 骑士的主要内容,如果未能解决你的问题,请参考以下文章