略。
\(\texttt{Data Range:}1\leq n\leq 80\)
首先考虑初始状态怎么算答案。很明显直接数满足的不好数,用总的减去不满足的还比较好做。注意到所有不满足的是一段 \(0\),所以就没了。
然后考虑怎么算从一个初始状态转移到一个目标状态的步数。考虑邻项交换,发现肯定是贪心匹配最优。
所以说如果有一个目标序列的话那么就可以算出初始序列到这个序列的步数和这个序列的答案,于是就可以直接 DP 目标序列。(这个时候我们 DP 未被保护的对数的最小值)
设 \(f_{i,j,k}\) 表示当前考虑到第 \(i\) 位并且钦定这一位填 \(1\),移动了 \(j\) 次并且这一段前缀中有 \(k\) 个 \(1\) 时未被保护对数的最小值。实际上转移的话考虑枚举下一个 \(1\) 在哪里,这个时候可以算出中间一段 \(0\) 对答案的贡献和需要移动的步数,就能够转移了。
最后处理的时候枚举一下最终序列中最后一个 \(1\) 填的哪里就好了,时间复杂度 \(O(n^5)\)。由于我比较菜所以不会斜率优化做法。
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
typedef long long int li;
const ll MAXN=85;
ll n,tot,res,m,r,c;
ll x[MAXN],cnt[2],pos[MAXN],f[MAXN][MAXN*MAXN][MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline void chkmin(ll &x,ll y)
{
x=x<y?x:y;
}
inline ll binom(ll n)
{
return n*(n-1)/2;
}
int main()
{
m=binom(n=read());
for(register int i=1;i<=n;i++)
{
cnt[x[i]=read()]++;
x[i]?pos[cnt[1]]=i:1;
}
if(!cnt[0])
{
for(register int i=0;i<=m;i++)
{
printf("%d ",0);
}
return 0;
}
res=tot=binom(cnt[0]),memset(f,0x3f,sizeof(f));
for(register int i=1;i<=n;i++)
{
f[i][abs(pos[1]-i)][1]=binom(i-1);
for(register int j=0;j<=m;j++)
{
for(register int k=1;k<=i&&k<cnt[1];k++)
{
r=cnt[1]-k;
if(f[i][j][k]!=0x3f3f3f3f)
{
for(register int l=i+1;l<=n-r+1;l++)
{
c=j+abs(pos[k+1]-l);
if(c<=m)
{
chkmin(f[l][c][k+1],f[i][j][k]+binom(l-i-1));
}
}
}
}
}
}
for(register int j=0;j<=binom(n);j++)
{
for(register int i=cnt[1];i<=n;i++)
{
if(f[i][j][cnt[1]]!=0x3f3f3f3f)
{
chkmin(res,f[i][j][cnt[1]]+binom(n-i));
}
}
printf("%d ",tot-res);
}
}
手机扫一扫
移动阅读更方便
你可能感兴趣的文章