动规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;
}