LOJ#3212. 「CSP-S 2019」划分
考场上初一的我用链表瞎模拟得到了 \(4\text{pts}\) 的好成绩,时隔一年半,不看题解还是只会 \(36\text{pts}\),自闭了……
4pts
输出样例。
12pts
暴力枚举分段点。
36pts
记 \(f(i,j)\) 表示当前新划分的区间是 \([i,j]\),划分完 \(1\sim j\) 的最小代价,那么有转移:
\[f(i,j)=\min_{\sum\limits_{t=k}^{i-1}a_t\le \sum\limits_{t=i}^ja_t}\left\{f(k,i-1)+\left(\sum_{t=i}^{j}a_t\right)^2\right\}
\]
时间复杂度 \(\mathcal O(n^3)\),期望得分 \(36\text{pts}\)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N=410;
int sum[N],a[N],f[N][N];
signed main()
{
freopen("partition.in","r",stdin);
freopen("partition.out","w",stdout);
int n,t;scanf("%lld%lld",&n,&t);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;i++) f[1][i]=sum[i]*sum[i];
for(int i=2;i<=n;i++)
{
for(int j=i;j<=n;j++)
{
for(int k=1;k<i;k++)
if(sum[j]-sum[i-1]>=sum[i-1]-sum[k-1])
f[i][j]=min(f[i][j],f[k][i-1]);
f[i][j]+=(sum[j]-sum[i-1])*(sum[j]-sum[i-1]);
}
}
int ans=0x3f3f3f3f3f3f3f3fll;
for(int i=1;i<=n;i++)ans=min(ans,f[i][n]);
printf("%lld",ans);
return 0;
}
64pts
一个显然的性质:最后一段的和越小越好。那么可以将状态转为一维,\(f(i)\) 表示最后一个划分结尾为 \(i\),\(1\sim i\) 的最少代价,\(l_i\) 表示满足转移条件的最大的 \(j\),那么有转移:
\[f(i)=\min_{0<j\le i\text{ 且 }\sum\limits_{t=l_j+1}^ja_t\le \sum\limits_{t=j+1}^i a_t}\left\{f(j)+\left(\sum_{t=j+1}^i a_t\right)^2\right\}
\]
时间复杂度 \(\mathcal O(n^2)\),期望得分 \(64\text{pts}\)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N=5e5+10;
int a[N],f[N],l[N],sum[N];
signed main()
{
freopen("partition.in","r",stdin);
freopen("partition.out","w",stdout);
int n,t;scanf("%lld%lld",&n,&t);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
memset(f,0x3f,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<i;j++)
{
if(sum[i]-sum[j]>=sum[j]-sum[l[j]])
{
if(f[j]+(sum[i]-sum[j])*(sum[i]-sum[j])<f[i])
{
f[i]=f[j]+(sum[i]-sum[j])*(sum[i]-sum[j]);
l[i]=j;
}
}
}
}
// for(int i=1;i<=n;i++)printf("%lld ",f[i]);puts("");
printf("%lld",f[n]);
return 0;
}
88pts
将 \(\sum\limits_{t=l_j+1}^ja_t\le \sum\limits_{t=j+1}^i a_t\) 写成前缀和的形式,得到:
\[s_j-s_{l_j}\le s_i-s_j
\]
移项可得:
\[s_i\ge 2s_j-s_{l_j}
\]
现在左边只和 \(i\) 相关,右边只和 \(j\) 相关,而我们只需要一个满足这个条件的最大的 \(j\),所以可以单调队列维护 \(2s_j-s_{l_j}\),时间复杂度 \(\mathcal O(n)\),期望得分 \(88\text{pts}\)。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N=5e5+10;
int a[N],f[N],l[N],sum[N],que[N],h=0,t=0;
signed main()
{
freopen("partition.in","r",stdin);
freopen("partition.out","w",stdout);
int n,t;scanf("%lld%lld",&n,&t);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
for(int i=1;i<=n;i++)
{
while(h<t&&sum[que[h+1]]*2-sum[l[que[h+1]]]<=sum[i])h++;
l[i]=que[h];f[i]=f[l[i]]+(sum[i]-sum[l[i]])*(sum[i]-sum[l[i]]);
while(h<t&&sum[que[t]]*2-sum[l[que[t]]]>=sum[i]*2-sum[l[i]])t--;
que[++t]=i;
}
printf("%lld",f[n]);
return 0;
}
100pts
卡常。
不懂为什么要出这个部分分。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=4e7+10;
typedef long long ll;
ll sum[N];
int a[N],la[N],que[N],h=0,t=0;
int b[N],l[N],r[N],p[N];
void Write(__int128 n)
{
if(n<0) {putchar('-');n=-n;}
if(n>9) Write(n/10);
putchar(n%10+'0');
return;
}
signed main()
{
freopen("partition.in","r",stdin);
freopen("partition.out","w",stdout);
int n,t;scanf("%d%d",&n,&t);
if(t==0)
{
for(int i=1;i<=n;i++)scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
for(int i=1;i<=n;i++)
{
while(h<t&&sum[que[h+1]]*2-sum[la[que[h+1]]]<=sum[i])h++;
la[i]=que[h];
while(h<t&&sum[que[t]]*2-sum[la[que[t]]]>=sum[i]*2-sum[la[i]])t--;
que[++t]=i;
}
__int128 ans=0;
int pos=n;
while(pos)
{
ans+=(__int128)(sum[pos]-sum[la[pos]])*(sum[pos]-sum[la[pos]]);
pos=la[pos];
}
Write(ans);
}
else
{
int x,y,z,m;scanf("%d%d%d%d%d%d",&x,&y,&z,&b[1],&b[2],&m);
for(int i=1;i<=m;i++) scanf("%d%d%d",&p[i],&l[i],&r[i]);
int n=p[m];
for(int i=3;i<=n;i++) b[i]=((ll)x*b[i-1]+(ll)y*b[i-2]+z)%(1ll<<30ll);
for(int i=1;i<=m;i++)
for(int j=p[i-1]+1;j<=p[i];j++)
a[j]=(b[j]%(r[i]-l[i]+1))+l[i],sum[j]=sum[j-1]+a[j];
for(int i=1;i<=n;i++)
{
while(h<t&&sum[que[h+1]]*2-sum[la[que[h+1]]]<=sum[i])h++;
la[i]=que[h];
while(h<t&&sum[que[t]]*2-sum[la[que[t]]]>=sum[i]*2-sum[la[i]])t--;
que[++t]=i;
}
__int128 ans=0;
int pos=n;
while(pos)
{
ans+=(__int128)(sum[pos]-sum[la[pos]])*(sum[pos]-sum[la[pos]]);
pos=la[pos];
}
Write(ans);
}
return 0;
}