题解:CF1666J Job Lookup
被迫来写篇题解。
首先,第一个要求我们只需要在递归构造的时候保证子树对应区间连续即可,现在考虑第二个要求。
就题目中的二叉树而言,想要确定其结构,我们只需要关注这段区间,即这棵子树根节点的编号,又因为子树区间连续,所以我们不难想到区间动态规划。
设 \(dp_{l,r}\) 表示 \(l\sim r\) 这段区间的答案,结合上文所说,我们可以枚举区间断点 \(k\) 表示这段区间的根节点,这样 \(dp_{l,r}\) 可以由 \(dp_{l,k-1}\) 和 \(dp_{k+1,r}\) 合并过来。
具体的,我们可以把 \(d_{i,j}\) 拆分开来,以左子树为例,其中的每一条边都会和子树外的每一条边产生贡献,即 \(\sum_{i=l}^{k-1}(\sum_{j=1}^{l-1}c_{i,j}+\sum_{j=k}^{n}c_{i,j})\),右子树同理。
输出的话我们可以记录最优状态下的根节点,记录一下,递归构建树。
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read()
{
int w=1,s=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch)){s=s*10+(ch-'0');ch=getchar();}
return w*s;
}
const int mod=998244353;
const int maxn=3e3+100;
const int inf=3e17+7;
int n,c[300][300];
int dp[300][300],ans[300][300];
int sum[300][300],fa[300];
inline int ask(int l1,int r1,int l2,int r2)
{if(l1>r1||l2>r2)return 0;return sum[r1][r2]-sum[l1-1][r2]-sum[r1][l2-1]+sum[l1-1][l2-1];}
void dfs(int l,int r,int rt)
{
if(l>r)return ;
int k=ans[l][r];
fa[k]=rt;
dfs(l,k-1,k);
dfs(k+1,r,k);
}
signed main()
{
#ifdef Lydic
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
// #else
// freopen("Stone.in","r",stdin);
// freopen("Stone.out","w",stdout);
#endif
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
c[i][j]=read();
sum[i][j]=c[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
}
for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++)
dp[i][j]=inf;
for(int i=1;i<=n;i++)
ans[i][i]=i;
for(int len=1;len<n;len++)
{
for(int l=1;l<=n-len;l++)
{
int r=l+len;
for(int k=l;k<=r;k++)
{
int res=ask(l,k-1,1,l-1)+ask(l,k-1,k,n)+ask(k+1,r,1,k)+ask(k+1,r,r+1,n)+dp[l][k-1]+dp[k+1][r];
if(res<dp[l][r])
{
dp[l][r]=res;
ans[l][r]=k;
}
}
}
}
dfs(1,n,0);
for(int i=1;i<=n;i++)printf("%lld ",fa[i]);
return 0;
}