Educational Codeforces Round 151 (Rated for Div. 2) E

链接

凌晨两点半突然醒了。。然后睡不着了。。躺了一个半小时决定起来啃题解。
花了一个小时弄懂了。但是要怎么自己想到还没想好。这个属于计数dp的范围了,我不是很熟悉了。

题目大意:

有n个盒子,里面装了一些球,球的数量大于等于1且小于n。
可以进行一种操作,每次操作可以把一个球移动到一个相邻的没有球的盒子里。
你一共可以进行k次操作,问,这k次操作全部执行完毕可以产生多少种不同的球的分布情况。

数量可能很大,答案mod1e9

有什么想法?
dp不好做吧。假如我要做dp,我要如何记录状态?
不好记录。要先尝试总结出来这个题目的特征。(感觉2500之后的题目,这一步绝对是必要的步骤)
可以发现,我们可以使用两次操作并且使得序列不变。
这让统计要求的不重不漏更加复杂。

但是我们其实可以发现,重复的情况一定是由一个球先向左再向右移动而产生的。否则是绝对不可能发生重复的。
到这里就是我能够学到的第一个思路
既然一定是由这种结构产生的,也就是,我可以先统计所有至少需要使用了\(k'\)次操作才能产生的情况数。
根据刚刚发现的,其实使用\(k'\)次能够产生的,就是所有小于\(k'\)且和\(k'\)奇偶性相同的方案数之和。

而这种计算的最重要的地方在于,这是可以作为状态的。很明显,我们的dp很容易就能够从比\(k'\)小的地方转移到\(k'\),因为我们前面统计的答案都是有最优属性存在的,所有想要继续保持这个属性并不难。

这个dp写多了应该是有感觉的,不算难想。如果给出了这个统计至少需要使用了\(k'\)次操作才能产生的情况数的思路的话。

那么我们其实就能够写出dp的状态与方程了。
啊,好像还有一些特点需要分析一下。
首先,两个球的移动轨迹是不可能相交的。也就是原本初始状态下的第\(i\)个球,在外面的答案统计里面,也是第\(i\)个球。
第二,球是单向移动的。否则就无法满足不重不漏了,这个是上面的分析。

那么其实我们的决策就是决策一个球放在哪里。

\(f[i][j][k]\)表示前\(i\)个盒子,已经安排好了\(j\)个球了,且使用了\(k\)次机会时,使得这\(k\)次机会没有浪费能够产生多少种方案数。

转移就是2种。一种是第\(i\)个盒子不放球。直接\(f[i][j][k]+=f[i-1][j][k]\)
一种是第\(i\)个盒子放第\(j\)个球\(f[i][j][k]+=f[i-1][j-1][k-abs(c[j]-i)]\)
其中\(c[j]\)表示第\(j\)个球的初始位置。

可以发现,这个dp是\(O(n^2k)\)的。
很不幸。\(n,k\leq 1500\)
刚好喵。被卡了。

更不幸的是,我的dp经验告诉我这种转移,还是计数dp的转移,不会有常规dp的优化方法。

转移没法优化。那也就是状态要改。

完全没有思路啊。。。让我自己想的话。
这还能怎么优化。。。这能优化是真离谱。

虽然看完了题解能够理解,但是我自己要能想到。。。也太难了。
cf给的题解不适合直接想。优化的方案绝对不止这一个。写完我去看看。
启发性极弱。

做法是这样的。
通过上面的分析,我们其实可以再发现一个很重要的性质。
小球的移动其实可以再次简化。每个位置其实我们可记录经过这个位置向左和经过这个位置向右的小球的个数。
在我们上面的dp求的答案中,一个位置不可能同时有向左移动的球和有向右移动的球,很明显这样是不优秀的。这样并不满足我们最短的需求。
于是第二维的状态可以进行更换,\(f[i][j][k]\)表示前\(i\)个盒子,当前位置经过的球有\(j\)个,且使用了\(k\)次机会时,使得这\(k\)次机会没有浪费能够产生多少种方案数。其中,第二维是带正负的,就是可以理解为\(j<0\)时,表示当前有球需要经过这里从右边移动到左边,而当\(j>0\)表示有球需要经过这里从左边移动到右边。

这样的状态和上面的状态是等价的。其实确实也是可以发现最开始的状态是有大量的空间和时间浪费的,有大量的明显不合法的情况被尝试转移。虽然这方面并不够支持我想到这个做法,但是也是一个思路吧。

那么,即使是这个状态,也是\(O(n^2k)\)的啊,第二维的变化并不能改变什么。
我们考虑第二维度的大小和转移。
可以发现一个等式,对于一个完整的状态,经过每个位置的移动的总数的和一定等于\(k\),也就是\(\sum j= k\),而,\(j\)的转移,每个位置其实和它转移而来的位置的数值上相差只能是1 。也就是\(j\)的序列其实最大最大的情况就是,假设长度为\(t_j\) , 就是\(1+2+...+t_j+t_j-1 +t_j-2+...+2+1\)。那么,就可以高兴的发现,我们可以根据这写出一个不等式 ,\((t_j+1)\times t_j \leq k\),而这个不等式的解,是\(t_j\leq 55\) 。第二维度的大小不会超过\(55\times 2\)。乘\(2\)是因为正负的原因。
那么,第二维原本的\(O(k)\),就被优化到了\(O(\sqrt k)\),而整体的复杂度就是\(O(nk^{1.5})\)
可以通过。

对于E的总结

  1. 其实第一个dp,我就是很难想到了。我对于这个没有任何状态划分的思路。这个状态设计的最重要的思路,就是求最小的,而不是所有的。想到了这个,做dp才是可能的。
  2. 小球的移动是单向的而且不可交叉。要是能想到第一点这一点不可能想不到,但是同样也是非常重要的。
  3. 而想到了上面两点,也只能做出第一\(n,k\leq1000\),而1500真的太恐怖了。想不到,这个优化。
  4. 第二个dp,最重要的是需要注意到第二维度状态的时空浪费,以及移动方向不交叉的性质。这还是我第一次利用这个优化dp。
    很独特,也很有意思。值得记录。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
	int a=0,b=1;char c=getchar();
	for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
	for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
	return a*b;
}
int f[2][120][1501];
int n,K,c[1501],a[1501],m;
const int Mod=1e9+7;
int main()
{
	n=read();K=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		if(a[i])c[++m]=i;
	}
	f[0][55][0]=1;
	for(int I=1;I<=n;I++)
	{
		int i=I%2;
		for(int j=0;j<=110;j++)
		{
			int now=j-55;
			for(int k=0;k<=K;k++)
			{
				f[i][j][k]=0;
				if(a[I])
				{
					if(k-abs(now)>=0)//the ball in the place won't be moved  
						f[i][j][k]+=f[i^1][j][k-abs(now)];
					f[i][j][k]%=Mod;
					if(k-abs(now)>=0&&j-1>=0)
						f[i][j][k]+=f[i^1][j-1][k-abs(now)];
				}
				else
				{
					if(k-abs(now)>=0)
						f[i][j][k]+=f[i^1][j+1][k-abs(now)];
					f[i][j][k]%=Mod;
					if(k-abs(now)>=0)
						f[i][j][k]+=f[i^1][j][k-abs(now)];
				}
				f[i][j][k]%=Mod;
			}
		}
//		cout<<f[i][56][1]<<' ';
	}
//	cout<<endl;
//	cout<<f[1][56][1]<<endl; 
	ll ans=0;
	for(int i=0;i<=K;i++)
	{
		if((i%2)==(K%2))
		{
			ans+=f[((n)%2)][55][i];
			ans%=Mod;
		}
	}
	cout<<ans%Mod<<endl;
	return 0;
}
posted @ 2024-05-29 17:20  HL_ZZP  阅读(3)  评论(0编辑  收藏  举报