【DP】DMOPC '21 Contest 8 P5 - Tree Building

发布时间 2023-07-13 19:53:16作者: CharlieVinnie

Problem Link

给定 \(n,m\) 和一个长为 \(m\) 的代价序列,对于一棵 \(n\) 个节点,每个节点度数不超过 \(m\) 的树,定义它的代价为 \(\sum\limits_{i=1}^n a_{deg_i}\)。求代价最小的树的代价。

\(n\le 10^9,m\le 3000,1\le a_i\le 10^9\)


首先一眼变成选出 \(n\)\(a\) 的和为 \(2n+2\)。经典的背包问题,价值值域和物品数都很大,但是体积值域很小,并且最终要求体积和是一个定值。

套路:如果 \(n\) 是偶数,那么对于任意一种方案,可以将选出的 \(n\) 个物品分成两部分,使得两部分体积之差的绝对值不超过 \(m\)

证明:滑动窗口,从左滑到右,差的符号改变了,而每次改变量是 \(a_i-a_j\),所以一定存在一个时刻差的绝对值不超过 \(m\)

于是当 \(n\) 是偶数时,定义 \(\mathrm{solve}(n,L,R)\) 表示解决 \(n\) 个物品,对于要求总体积在 \([L,R]\) 之间的情况求解,那么可以递归到 \(\mathrm{solve}(n/2,(L-m)/2,(R+m)/2)\) 来做,然后自己和自己合并。一共 \(\log\) 层,每层长度至多增加 \(m\),所以复杂度可以接受。

\(n\) 是奇数时,直接拎出最后一个数枚举,递归到 \(n-1\)

计算时间复杂度。发现每次区间长度会 \(+m\) 然后 \(/2\),所以每一层加的 \(m\) 会在后面以 \(m,m/2,m/4,\dots\) 分布,即每层被加的 \(m\) 是从上面来的 \(m,m/2,m/4,\dots\),总和为 \(O(m)\)。所以总时间复杂度为 \(O(m^2\log n)\)

点击查看代码
// https://dmoj.ca/problem/dmopc21c8p5
//~ #define _GLIBCXX_DEBUG
#include <bits/stdc++.h>
#define For(i,a,b) for(int i=a;i<=b;i++)
#define Rev(i,a,b) for(int i=a;i>=b;i--)
#define Fin(file) freopen(file,"r",stdin);
#define Fout(file) freopen(file,"w",stdout);
using namespace std;
const int N=3e3+5; using ll = long long;
int m,a[N];
vector<ll> solve(int n,int L,int R){
	//~ printf("solve(%d,%d,%d)\n",n,L,R);
	vector<ll> res(R-L+1,1e18);
	if(n==1) For(i,L,R) res[i-L]=1<=i&&i<=m?a[i]:1e18;
	else if(n&1){
		int l=max(L-m,1); vector<ll> vec=solve(n-1,l,R);
		For(j,l,R) For(k,max(j+1,L),min(j+m,R)) res[k-L]=min(res[k-L],vec[j-l]+a[k-j]);
	}
	else{
		int l=max((L-m)/2,1),r=(R+m)/2; vector<ll> vec=solve(n/2,l,r);
		For(j,l,r) For(k,max(l,L-j),min(r,R-j)) res[j+k-L]=min(res[j+k-L],vec[j-l]+vec[k-l]);
	}
	return res;
}
int main(){
	int n; cin>>n>>m; For(i,1,m) cin>>a[i];
	cout<<solve(n,2*n-2,2*n-2)[0]<<'\n';
	return 0;
}