树形DP
解决符合子树最优结构,或者链上问题(枚举链的最高点)等等
例题1
Luogu P3576 [POI2014]MRO-Ant colony
蚂蚁没有办法走回头路,且行动是可以预知的,只需要考虑经过食蚁兽所在的边,所以不妨倒着做,从食蚁兽出发得出每个可能的区间,然后排序后二分即可
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+5;
int cnt,to[N<<1],nxt[N<<1],he[N],R[N],L[N],son[N],a[N],n,m,k;
ll ans;
inline void add(int u,int v) {
to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt;
}
inline void dp(int u,int siz,int l,int r) {
if((ll)siz*l>a[m]) return;
L[u]=siz*l;
if((ll)siz*(r+1)-1>a[m]) R[u]=a[m];
else R[u]=siz*(r+1)-1;
}
void dfs(int fa,int u) {
for(int e=he[u];e;e=nxt[e]) {
int v=to[e];
if(v!=fa) {
dp(v,(son[v]==0?1:son[v]),L[u],R[u]);
if(L[v]!=0||R[v]!=0) {
dfs(u,v);
}
}
}
}
inline int left(int x) {
int l=1,r=m,ans;
while(l<=r) {
int mid=l+r>>1;
if(a[mid]>=x) ans=mid,r=mid-1;
else l=mid+1;
}
return ans;
}
inline int right(int x) {
int l=1,r=m,ans=0;
while(l<=r) {
int mid=l+r>>1;
if(a[mid]<=x) ans=mid,l=mid+1;
else r=mid-1;
}
return ans;
}
int main() {
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++) {
scanf("%d",&a[i]);
}
sort(a+1,a+m+1);
int rt=0,Fa=0;
for(int i=1;i<n;i++) {
int u,v; scanf("%d%d",&u,&v);
add(u,v),add(v,u);
son[u]++,son[v]++;
if(i==1) {
rt=u,Fa=v;
}
}
for(int i=1;i<=n;i++) son[i]--;
dp(rt,(son[rt]==0?1:son[rt]),k,k);
dp(Fa,(son[Fa]==0?1:son[Fa]),k,k);
dfs(Fa,rt),dfs(rt,Fa);
for(int i=1;i<=n;i++) {
// if(!son[i]) printf("%d %d\n",L[i],R[i]);
if(!son[i]&&(L[i]||R[i])) {
ans+=right(R[i])-left(L[i])+1;
}
}
printf("%lld\n",ans*k);
return 0;
}
例题2
显然,如果一个点权值确定,则整棵树都确定了且是唯一的,不妨用根节点的权值表示树的权值
所以对于每个点权求出其对应的根节点的权值,用\(log\)改为加法,排序判断最小修改次数
#include<bits/stdc++.h>
#define db double
const db eps=1e-7;
using namespace std;
const int N=1e6+5;
int n,cnt,to[N],nxt[N],he[N],a[N];
db f[N];
inline void add(int u,int v) {
to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt;
}
void dfs(int fa,int u) {
int son=0;
for(int e=he[u];e;e=nxt[e]) {
int v=to[e];
if(v!=fa){
dfs(u,v);
son++;
}
}
if(son) f[u]=log(son);
}
void dgs(int fa,int u) {
f[u]+=f[fa];
for(int e=he[u];e;e=nxt[e]) {
int v=to[e];
if(v!=fa) {
dgs(u,v);
}
}
f[u]=f[fa]+log(a[u]);
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
}
for(int i=1;i<n;i++) {
int u,v; scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
dfs(0,1); dgs(0,1);
sort(f+1,f+n+1);
int ans=n,now=1;
for(int i=2;i<=n;i++) {
if(abs(f[i]-f[i-1])<=eps) now++;
else {
ans=min(ans,n-now);
now=1;
}
}
ans=min(ans,n-now);
printf("%d\n",ans);
return 0;
}
例题3
有向图求拓扑序种类是NP问题
设\(f[i]\)表示\(i\)子树的拓扑序的种类,显然是无法转移的
所以再记一维表示根节点的拓扑序
对于\((u,v)\),
-
若\(u\)先,则
\[f[u][k]=\sum_{i=1}^{k}f[u][i]\times C_{k-1}^{i-1}\times C_{sz[u]+sz[v]-k}^{sz[u]-i}\times f[v][j],j\in(k-i,sz[v]] \] -
若v先,则
\[f[u][k]=\sum_{k=1}^{k}f[u][i]\times C_{k-1}^{i-1}\times C_{sz[u]+sz[v]-k}^{sz[u]-i}\times f[v][j],j\in[1,k-i] \]
前缀和优化即可
#include<bits/stdc++.h>
#define ll long long
const int p=1e9+7;
using namespace std;
inline int mo(int x) {
return x>=p?x-p:x;
}
const int N=1005;
int cnt,n,to[N<<1],nxt[N<<1],he[N],w[N<<1];
ll f[N][N];
int siz[N],c[N][N];
char op[10];
inline void add(int u,int v,int k) {
to[++cnt]=v,nxt[cnt]=he[u],w[cnt]=k;
he[u]=cnt;
}
void dfs(int fa,int u) {
siz[u]=1; f[u][1]=1;
for(int e=he[u];e;e=nxt[e]) {
int v=to[e];
if(v!=fa) {
dfs(u,v);
if(!w[e]) {
for(int k=siz[u]+siz[v];k;k--) {
int sum=0;
for(int i=1;i<=min(siz[u],k);i++) {
ll t=mo(f[v][siz[v]]-f[v][k-i]+p);
sum=mo(sum+t*f[u][i]%p*c[i-1][k-1]%p*c[siz[u]-i][siz[u]+siz[v]-k]%p);
}
f[u][k]=sum;
}
} else {
for(int k=siz[u]+siz[v];k;k--) {
int sum=0;
for(int i=1;i<=min(siz[u],k);i++) {
ll t=f[v][min(siz[v],k-i)];
sum=mo(sum+t*f[u][i]%p*c[i-1][k-1]%p*c[siz[u]-i][siz[u]+siz[v]-k]%p);
}
f[u][k]=sum;
}
}
siz[u]+=siz[v];
}
}
for(int i=2;i<=siz[u];i++) {
f[u][i]=mo(f[u][i]+f[u][i-1]);
}
}
int main() {
int T; scanf("%d",&T);
c[0][0]=1;
for(int j=1;j<=1000;j++) {
c[0][j]=1;
for(int i=1;i<=j;i++) {
c[i][j]=mo(c[i][j-1]+c[i-1][j-1]);
}
}
while(T--) {
scanf("%d",&n);
for(int i=1;i<n;i++) {
int u,v;
scanf("%d%s%d",&u,op,&v);
++u,++v;
add(u,v,op[0]=='>'),add(v,u,op[0]=='<');
}
dfs(0,1);
printf("%d\n",f[1][n]);
cnt=0;
for(int i=1;i<=n;i++) he[i]=0;
memset(f,0,sizeof(f));
memset(siz,0,sizeof(siz));
}
return 0;
}
树形DP+贪心(扰动法)
Luogu P3574 [POI2014]FAR-FarmCraft
显然可以树形DP,\(f[i]\)表示\(i\)的子树中的最大装配时间,\(g[i]\)表示\(i\)的赶路时间
考虑如何转移\(f\),貌似要\(O(n!)\)
然后利用扰动法得出:(交换相邻两项使之变劣)
\[f[v1]-g[v1]>=f[v2]-g[v2] \]#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int cnt,to[N<<1],nxt[N<<1],he[N],n,c[N],f[N],g[N];
struct A{ int u,s; };
bool operator <(A i,A j) {
return i.s<j.s;
}
priority_queue<A>q;
inline void add(int u,int v) {
to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt;
}
void dfs(int fa,int u) {
for(int e=he[u];e;e=nxt[e]) {
int v=to[e];
if(v!=fa) dfs(u,v);
}
for(int e=he[u];e;e=nxt[e]) {
int v=to[e];
if(v!=fa) q.push((A){v,f[v]-g[v]});
}
f[u]=c[u];
while(!q.empty()) {
int v=q.top().u; q.pop();
f[u]=max(f[u],f[v]+g[u]+1);
g[u]+=g[v]+2;
}
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%d",&c[i]);
}
for(int i=1;i<n;i++) {
int u,v; scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
dfs(0,1);
printf("%d\n",max(f[1],c[1]+g[1]));
return 0;
}
标签:nxt,cnt,sz,int,fa,树形,DP,he 来源: https://www.cnblogs.com/wsfwsf/p/14636638.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。