摩天大楼
将(N)个互不相同的整数(A_1, A_2, ⋯ , A_N)任意排列成(B_1, B_2, ⋯ , B_N),要求(sum_{i=1}^{N−1}|B_{i+1} − B_i| ≤ L)。
计数方案数。
(N ≤ 100, L ≤ 1000)
题解
仓鼠《动态规划选讲》。
考虑一个把所有元素按照从大到小的顺序放到排列中正确的位置的过程。那么每次插入一个元素,其所在的连续段就是以其为根在笛卡尔树上的子树。
把原序列排序后做个差分,那么在这个过程中,每个时刻序列都形成了若干个连续段。一个连续段的端点和其相邻的还未被填上的数的差值会被拉大,拉大的数值就是当前差分的值。
所以可以记录形成的连续段的情况。设(F[i][s][x][a ∈ {0,1}][b ∈{0,1}])表示考虑到第(i)个元素,当前差值的和为(s),连续段的个数为(x),序列的左右端点有没有被加入。可以根据这些信息算出,被拉大差值的两个相邻元素的数目。每次加入元素分情况讨论:新建段、紧贴着一个段、合并了两个段。
可以把这个过程看做笛卡尔树上的子树合并。然而这个转化并没有什么用……
时间复杂度(O(N^2L))。
https://www.cnblogs.com/coldchair/p/12207336.html
CO int N=101,M=1001;
int A[N],F[N][N][M][3];
int main(){
int n=read<int>(),m=read<int>();
for(int i=1;i<=n;++i) read(A[i]);
if(n==1) {puts("1"); return 0;}
sort(A+1,A+n+1);
F[0][0][0][0]=1;
for(int i=1;i<=n;++i){
int det=A[i]-A[i-1];
for(int j=0;j<=i;++j)for(int k=0;k<=m;++k)
for(int u=0;u<=2;++u)if(F[i-1][j][k][u]){
int nk=k+det*(2*j-u);
if(nk>m) continue;
int f=F[i-1][j][k][u];
chkadd(F[i][j+1][nk][u],mul(f,j-u+1)); // new
if(u<2) // new in the border
chkadd(F[i][j+1][nk][u+1],mul(f,1+!u));
chkadd(F[i][j][nk][u],mul(f,2*j-u)); // stick
if((j>u or i==n) and u<2) // stick in the border
chkadd(F[i][j][nk][u+1],mul(f,1+!u));
if(j>1 and !(j==2 and u==2 and i<n)) // merge
chkadd(F[i][j-1][nk][u],mul(f,j-1));
}
}
int ans=0;
for(int k=0;k<=m;++k)
chkadd(ans,F[n][1][k][2]);
printf("%d
",ans);
return 0;
}
摆花
小明的花店新开张,为了吸引顾客,他想在花店外摆上一圈花。小明有(n)盆花,从(1)到(n)编号。花店外恰有(n)个位置可供摆花,这些位置也从(1)到(n)编号。规定位置(i)的顺时针下一个位置为(i+1)((1leq ileq n-1)),特别的,位置(n)的顺时针下一个位置为位置(1)。
通过调查顾客的喜好,小明给每盆花(i)设定了四个参数:(a_i,b_i,c_i,d_i),对于一种摆花方案,小明按如下方法计算该方案的美观度:
-
设编号为(i)的花所在位置的顺时针下一个位置摆的是编号为(j)的花,且(i< j),则花(i)的美观度为(a_i+b_j);
-
设编号为(i)的花所在位置的顺时针下一个位置摆的是编号为(j)的花,且(i>j),则花(i)的美观度为(c_i+d_j);
-
该方案的美观度等于所有花的美观度之和。
小明要在每个位置上摆一盆花,请计算所有摆花方案中,最大的美观度是多少。
(nleq 2000)。
题解
按照编号从小到大摆花,维护连续段即可。因为这是一个环,并且这还是个最优化问题,所以代码相当的简单。
时间复杂度(O(n^2))。
CO int N=2e3+10;
int a[N],b[N],c[N],d[N];
int F[N][N];
int main(){
int n=read<int>();
for(int i=1;i<=n;++i) read(a[i]),read(b[i]),read(c[i]),read(d[i]);
for(int i=1;i<=n;++i)for(int j=0;j<=i-1;++j){
F[i][j+1]=max(F[i][j+1],F[i-1][j]+d[i]+a[i]);
if(j) F[i][j]=max(F[i][j],F[i-1][j]+max(b[i]+a[i],d[i]+c[i]));
if(j>=2 or (i==n and j==1)) F[i][j-1]=max(F[i][j-1],F[i-1][j]+b[i]+c[i]);
}
printf("%d
",F[n][0]);
return 0;
}
AppleTrees
有一排(D)个坑,要种下(n)棵树,每棵树有个半径(r_i),表示它左右(r_i)的范围内不能种其它树。求合法的方案数。
(D ≤ 100000, n ≤ 40, r_i ≤ 40)。
题解
任轩笛《杂题选讲》。
如果知道了第一棵树和最后一棵树之间的最小距离,只要做个插板法,组合数把所有树分配到(D)个坑里就可以了。
假如已经确定了一个排列,第一棵树和最后一棵树的最小距离就是(sum max(r_i, r_{i+1}))。
按照(r_i)从小到大进行DP,在算到相邻两棵树中较大的时把贡献算进答案。
(F[i, j, k])表示考虑了前(i)棵树,中间还有(2*j)个空位(左右两端一定有空位),距离贡献和为(k)的方案数。
转移时讨论这棵树放在开头/结尾/中间、左右分别是否要占据空位。
复杂度(O(n^3 ∗ r_i))。
CO int N=1e5+10,M=50;
int fac[N],ifac[N];
int F[M][M][M*M];
IN int C(int n,int m){
return mul(fac[n],mul(ifac[m],ifac[n-m]));
}
struct AppleTrees{
int theCount(int D,vector<int> R){
fac[0]=1;
for(int i=1;i<=D;++i) fac[i]=mul(fac[i-1],i);
ifac[D]=fpow(fac[D],mod-2);
for(int i=D-1;i>=0;--i) ifac[i]=mul(ifac[i+1],i+1);
int n=R.size(),lim=(n-1)**max_element(R.begin(),R.end());
R.push_back(0),sort(R.begin(),R.end());
F[1][0][0]=1;
for(int i=2;i<=n;++i)for(int j=0;j<=i-1;++j)
for(int k=0;k<=lim;++k)if(F[i-1][j][k]){
F[i][j+1][k]=add(F[i][j+1][k],mul(2,F[i-1][j][k]));
F[i][j][k+R[i]]=add(F[i][j][k+R[i]],mul(2,F[i-1][j][k]));
F[i][j+1][k]=add(F[i][j+1][k],mul(j,F[i-1][j][k]));
F[i][j][k+R[i]]=add(F[i][j][k+R[i]],mul(2*j,F[i-1][j][k]));
if(j) F[i][j-1][k+2*R[i]]=add(F[i][j-1][k+2*R[i]],mul(j,F[i-1][j][k]));
}
int ans=0;
for(int k=0;k<=lim and k<=D;++k)if(F[n][0][k])
ans=add(ans,mul(C(D-1-k+n,n),F[n][0][k]));
return ans;
}
}tmp;
//int main(){
// int D=100000;
// vector<int> R={21, 37, 23, 13, 32, 22, 9, 39};
// printf("%d
",tmp.theCount(D,R));
// return 0;
//}