LOJ#3212. 「CSP-S 2019」划分

Link

考场上初一的我用链表瞎模拟得到了 \(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;
}
posted @ 2021-05-01 21:54  zzt1208  阅读(86)  评论(0编辑  收藏  举报