把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷8100】[USACO22JAN] Counting Haybales P(DP)

题目链接

  • 给定一个长度为 \(n\) 的序列,可以无限交换相邻两个值恰好相差 \(1\) 的数。
  • 求可能得到多少种序列。
  • \(1\le n\le5\times10^3\)

可以交换的条件

发现两个数的顺序能改变,充要条件是 它们恰好相差 \(1\)它们之间所有数都与它们中的某一个恰好相差 \(1\) ,因为只有才能让它们两个相邻然后发生交换。这可以预处理出来。

仅从这个条件出发并不好做,因为状态什么的都很难表示。

考虑到既然要恰好相差 \(1\),那么一个必要条件就是奇偶性相同的数的相对顺序不能变。

所以先把所有数按奇偶性分成两部分,同一部分中的数相对顺序是确定的,只能一个一个按序加入最终序列。

即,设 \(f_{i,j}\) 表示已经填了 \(i\) 个奇数、\(j\) 个偶数的方案数。

每次转移时,只要保证这次填入的数能在上次填入的与它奇偶性不同的那个数后面即可。

代码:\(O(n^2)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 5000
#define X 1000000007
using namespace std;
int n,a[N+5],c[2],q[2][N+5],f[N+5][N+5],p[N+5][N+5];
int main()
{
	RI Tt,i,j,mn,mx;scanf("%d",&Tt);W(Tt--)
	{
		for(scanf("%d",&n),c[0]=c[1]=0,i=1;i<=n;++i) scanf("%d",a+i),q[a[i]&1][++c[a[i]&1]]=i;//按奇偶性分成两部分
		for(i=1;i<=n;++i) for(mn=mx=a[i],j=i;j<=n;++j)//预处理两个数顺序能否改变
			mn=min(mn,a[j]),mx=max(mx,a[j]),p[i][j]=abs(a[i]-a[j])<=1&&min(a[i],a[j])-mn<=1&&mx-max(a[i],a[j])<=1;//它们相差1;中间都与某个相差1
		for(i=0;i<=c[0];++i) for(j=0;j<=c[1];++j) f[i][j]=0;//清空
		#define OK(x,y) (x<y||p[y][x])//判断y能否在x后面
		for(f[0][0]=1,i=0;i<=c[0];++i) for(j=0;j<=c[1];++j)
			i^c[0]&&(!j||OK(q[1][j],q[0][i+1]))&&(f[i+1][j]=(f[i+1][j]+f[i][j])%X),//尝试填偶数
			j^c[1]&&(!i||OK(q[0][i],q[1][j+1]))&&(f[i][j+1]=(f[i][j+1]+f[i][j])%X);//尝试填奇数
		printf("%d\n",f[c[0]][c[1]]);
	}return 0;
}
posted @ 2022-02-22 20:05  TheLostWeak  阅读(82)  评论(0编辑  收藏  举报