动规B—背包&树形dp

A. 数字组合.

\(N\)个数中找出其和为 \(M\) 的若干个数,把满足条件的数字组合都找出来以统计组合的个数,输出组合的个数

类似0/1背包,恰好装满背包的方案数

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=110;
int a[N],c[1010];
signed main()
{
	// freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
	int n,m;
	cin>>n>>m;
	per(i,1,n) scanf("%d",a+i);
	c[0]=1;
	per(i,1,n) for(int j=m-a[i];j>=0;--j)//压维,倒着枚举
	{
		c[j+a[i]]+=c[j];
	}
	printf("%d\n",c[m]);
	return 0;
}

B. 自然数拆分

给定一个自然数 N,要求把 N 拆分成若干个正整数相加的形式,参与加法运算的数可以重复。
至少拆分成 2 个数的和。

完全背包(每个数可以用多次)

#include<bits/stdc++.h>
using namespace std;
#define per(i,a,b) for(int i(a);i<=b;++i)
const int N=4010,mod=2147483648;
long long c[N];
signed main()
{
	int n;
	cin>>n;
	c[0]=1;
	per(i,1,n-1)//至少拆分成两个数的和
    {
        per(j,i,n) /*正着枚举*/c[j]=(c[j]+c[j-i])%mod;//该数为j时,可以由哪些转移来
    }
	printf("%lld\n",c[n]);
	return 0;
}

C. 陪审团

\(f[i][j][k]\) 为在前 \(i\) 个人中选 \(j\) 个,p比d多 \(k\) ,D+P的最大值

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=210,t=400;
int p[N],d[N],f[N][22][810],q[22];
signed main()
{
	// freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
	int n,m;
	cin>>n>>m;
	per(i,1,n) scanf("%d%d",p+i,d+i);
	memset(f,-0x3f,sizeof(f));
	f[0][0][400]=0;
	per(i,1,n) per(j,0,m) per(k,0,800)//辩方总分d比控方总分p少k
	{
		f[i][j][k]=f[i-1][j][k];//当前人不选

		if(j - 1 < 0) continue;

		if(k+d[i]-p[i]<0||k+d[i]-p[i]>800) continue;
		f[i][j][k]=max(f[i][j][k],f[i-1][j-1][k-p[i]+d[i]]+d[i]+p[i]);//dp转移
	}
	int v=0;//找 |D−P| 最小。
	while(f[n][m][t-v]<0&&f[n][m][t+v]<0) ++v;
	if(f[n][m][t-v]>f[n][m][t+v]) v=t-v;
	else v=t+v;
	int ansp,ansd;
	ansp=(f[n][m][v]+abs(v-t))/2;
	ansd=f[n][m][v]-ansd;
	if(v<t) swap(ansp,ansd);
	printf("Best jury has value %d for prosecution and value %d for defence:\n",ansp, ansd);
	int j=m;
	for(int i=n;i>=1;--i)
	{
		if(f[i][j][v]==f[i-1][j][v]) continue;//逆推
		q[j--]=i;
		v+=d[i]-p[i];
	}
	per(i,1,m) printf("%d ",q[i]);
	puts("");
	return 0;
}

D. 硬币

给定 N (≤100)种硬币,其中第 i 种硬币的面值为 \(A_i\),共有 \(C_i\) (≤100) 个。
从中选出若干个硬币,把面值相加,若结果为 S,则称 “面值 S 能被拼成”。
求 1∼M (≤\(10^5\)) 之间能被拼成的面值有多少个。

多重背包

三重循环枚举,\(10^{10}\) TLE

per(i,1,n) for(int j=m;j>=a[i];--j) per(k,1,c[i])
{
    if(j-(a[i]*k)<0) break;
    if(!s[j]&&s[j-a[i]*k]) s[j]=1;
}
per(i,1,n) 
{
    per(j,0,m) cnt[j]=0;//表示当前的状态转移用了几个a[i]
    per(j,a[i],m) if(!s[j]&&s[j-a[i]]&&cnt[j-a[i]]<c[i]) s[j]=1,cnt[j]=cnt[j-a[i]]+1;//如果j的转移用a[i]了,+1
}
#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=110,M=100010;
int a[N],c[N],cnt[M];
bitset<M>s;
signed main()
{
	// freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
	int n,m,ans;
	cin>>n>>m;
	while(n||m)
	{
		per(i,1,n) scanf("%d",a+i);
		per(i,1,n) scanf("%d",c+i);
		s.reset();
		s[0]=1;
		per(i,1,n) 
		{
			per(j,0,m) cnt[j]=0;
			per(j,a[i],m) if(!s[j]&&s[j-a[i]]&&cnt[j-a[i]]<c[i]) s[j]=1,cnt[j]=cnt[j-a[i]]+1;
		}
		ans=0;
		per(i,1,m) if(s[i]) ++ans;
		printf("%d\n",ans);
		cin>>n>>m;
	}
	return 0;
}

E. 划分大理石

有价值分别为 1..6 的大理石各 a[1..6] 块,现要将它们分成两部分,使得两部分价值之和相等,问是否可以实现。
其中大理石的总数不超过 20000。

多重背包,恰好装满 \(tot/2\)

#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=10,M=120010;
int a[N],c[M];
signed main()
{
	// freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
	int n=6,x=0;
	per(i,1,n) scanf("%d",a+i),x+=a[i]*i;
	while(x)
	{
		if(x&1)//和为偶数,才有可能
		{
			puts("Can't");
			x=0;
			per(i,1,n) scanf("%d",a+i),x+=a[i]*i;
			continue;
		}
		x/=2;
		memset(c,0,sizeof(c));
		c[0]=1;
		per(i,1,n) for(int j=x;j>=0;--j)
		{
			for(int k=1;k<=a[i]&&k*i<=j;++k) c[j]+=c[j-i*k];//能否恰好装满 j
		}
		if(c[x]) puts("Can");
		else puts("Can't");
		x=0;
		per(i,1,n) scanf("%d",a+i),x+=a[i]*i;
	}
	return 0;
}

F. 周年庆宴

Ural大学有\(N\)个职员,编号为 \(1 - N\)。他们有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司,每个职员有一个快乐指数。

现在有个周年庆宴会,要求与会职员的快乐指数之和最大。但是,没有职员愿和直接上司一起与会。快乐指数\(R_i\)。(\(−128 \leq Ri\leq127\))

简化:一棵树,若父节点被选,其子节点都不能选,求被选的点的最大权值和

树形dp,\(f[x][0/1]\)表示选/不选 \(x\) 的最大权值和

f[x][0]+=max(0,max(f[y][1],f[y][0]));
f[x][1]+=max(0,f[y][0]);

G. 选课

课程具有拓扑序,构成树状结构,n门课程中只能选m门,每门课都有一定学分(选一个节点,就得选它的父节点),求最大学分

树形dp+背包选择

\(f[u][i]\) 表示节点 u 的子树中选 i 个点能获得的最大学分

void dfs(int u)
{
    for(int i=hd[x];i;i=nx[i])//枚举子树
    {
        dfs(to[i]);
        for(int j=m-1;j>=0;j--)//枚举体积
            for(int k=1;k<=j;k++)//子树中选点的个数
                f[u][j]=max(f[u][j],f[e[i]][k]+f[u][j-k]);
    }
    for(int i=m;i>=0;i--)//最后要将当前节点加入形成最终答案
        f[u][i]=f[u][i-1]+w[u];
}

H. 积蓄程度

AcWing287

参考:https://blog.csdn.net/m0_59273843/article/details/123967800

1.暴力:\(d[x]\) 为从x出发流向子树的最大流量,每一个点跑一遍dfs,O(\(n^2\))

2.优化:\(f[x]\) 表示以x为根的最大流量,\(f[1]=d[1]\),设y为x的子节点

\(f[y]=d[y]+ad\) 以y为根的子树的流量+沿父亲方向的流量

\(ad=f[x]-x流向y的\)

if(in[x] != 1 && in[to] != 1)
    ad = min(f[cur] - min(e[i].dis, d[to]), e[i].dis);//俩点都不是叶子
else ad = min(f[cur] - e[i].dis, e[i].dis);ad = e[i].dis;//to是叶子
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int n;
int head[maxn], cnt;
struct Edge {
	int to, dis, next;
}e[maxn * 2];
bool vis[maxn];
int d[maxn], f[maxn];
int in[maxn];
void add(int u, int v, int w) {
	++cnt;
	e[cnt].to = v;
	e[cnt].dis = w;
	e[cnt].next = head[u];
	head[u] = cnt;
}
int ans;
void dfs(int cur = 1, int fa = 0) {
	for(int i = head[cur]; i; i = e[i].next) {
		int to = e[i].to;
		if(to == fa) continue;
		dfs(to, cur);
		if(in[to] == 1) {
			d[cur] += e[i].dis;
		}
		else {
			d[cur] += min(e[i].dis, d[to]);
		}
	}
}
void dfs2(int cur = 1, int fa = 0) {
	for(int i = head[cur]; i; i = e[i].next) {
		int to = e[i].to;
		if(to == fa) continue;
		f[to] = d[to];
		int ad;
		if(in[cur] != 1 && in[to] != 1)
			ad = min(f[cur] - min(e[i].dis, d[to]), e[i].dis);
		else ad = min(f[cur] - e[i].dis, e[i].dis);
		f[to] += ad;
		ans = max(ans, f[to]);
		dfs2(to, cur);
	}
}
void solve() {
	cnt = 0;
	ans = 0;
	memset(f, 0, sizeof(f));
	memset(d, 0, sizeof(d));
	memset(in, 0, sizeof(in));
	memset(head, 0, sizeof(head));
	cin >> n;
	for(int i = 1; i < n; i++) {
		int u, v, w;
		cin >> u >> v >> w;
		in[u]++;
		in[v]++;
		add(u, v, w);
		add(v, u, w);
	}
	dfs(1, 0);
	f[1] = d[1];
	ans = f[1];
	dfs2(1, 0);
	cout << ans << endl;
}
int main()
{
	int t;
	cin>>t;
	while(t--) solve();
	return 0;
}//https://blog.csdn.net/m0_59273843/article/details/123967800

I. 战略游戏

城堡中的路形成一棵树。
他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能嘹望到所有的路。
注意,某个士兵在一个结点上时,与该结点相连的所有边将都可以被嘹望到。
给定一棵树,计算出他需要放置最少的士兵。

\(f[x][0/1]\)表示该点放/不放 士兵,子树被全覆盖所需最少士兵数

f[x][0]+=f[to[i]][1];//该点不放,子节点都得放
f[x][1]+=min(f[to[i]][0],f[to[i]][1]);//该点放,子节点随意

K. 计算机

N台电脑形成树形结构,学校的管理员想知道对于每一台电脑 \(i\),到与它距离最远的电脑的距离 \(Si\)

每个点的最远距离有两种情况:
1.最远的点在子树内
2.在父亲方向的另一个子树里

res[x]=max(ans[x][1],po);

\(ans[x][0/1]\) 表示 x 子树内最长/次长的距离
po为走父亲方向的最远距离,从父亲节点下传的

if(ans[x][1]==ans[to][1]+mp[x][i].po) p=max(po,ans[x][0]);//to在x的最长路径上,选次长路和po打擂台
else p=max(po,ans[x][1]);//不在最长路上
#include<bits/stdc++.h>
using namespace std;
int n,ans[10010][2],res[10010];
bool ck[10010];
struct aaa{
	int to,po;
} w;
vector<aaa>mp[10010];
void find(int x,int po)
{
	int to,p=0;
	res[x]=max(po,ans[x][1]);
	for(int i=0; i<mp[x].size(); i++)
	{
		to=mp[x][i].to;
		if(!ck[to])
		{
			ck[to]=1;
			if(ans[x][1]==ans[to][1]+mp[x][i].po) p=max(po,ans[x][0]);
			else p=max(po,ans[x][1]);
			p+=mp[x][i].po;
			find(to,p);
		}
	}
}
void dfs(int x)
{
	int to,p;
	for(int i=0; i<mp[x].size(); i++)
	{
		to=mp[x][i].to;
		if(!ck[to])
		{
			ck[to]=1;
			dfs(to);
			p=ans[to][1]+mp[x][i].po;
			if(p>ans[x][1])
			{
				ans[x][0]=ans[x][1];
				ans[x][1]=p;
			}
			else if(p>ans[x][0]) ans[x][0]=p;
		}
	}
}
int main()
{
	int i,y,l,to,po;
	cin>>n;
	for(i=2; i<=n; i++)
	{
		scanf("%d%d",&y,&l);
		w.to=i;
		w.po=l;
		mp[y].push_back(w);
		w.to=y;
		mp[i].push_back(w);
	}
	ck[1]=1;
	dfs(1);//预处理ans[x][0/1]
	res[1]=ans[1][1];
	memset(ck,0,sizeof(ck));
	ck[1]=1;
	for(i=0; i<mp[1].size(); i++)
	{
		to=mp[1][i].to;
		if(ans[1][1]==ans[to][1]+mp[1][i].po) po=ans[1][0];
		else po=ans[1][1];
		po+=mp[1][i].po;
		ck[to]=1;
		find(to,po);
	}
	for(i=1; i<=n; i++) printf("%d\n",res[i]);
	return 0;
}
posted @ 2023-02-03 20:59  f2021yjm  阅读(15)  评论(0编辑  收藏  举报