【模型转化】【dp/矩阵乘法】AGC013E - Placing Squares

6 篇文章 0 订阅
4 篇文章 0 订阅

题意:

你有一个长度为n的纸条,上面有m个标记,左端点与第i个标记的距离为x[i]。现在,你要在纸条上摆放一些(可以为一个)正方形,满足以下要求:
1:正方形边长为整数。
2:正方形的一条边必须接触纸条并且完全处于纸条上(不能超出纸条)。
3:纸条的任易位置都必须接触正方形(纸条被正方形完全覆盖)。
4:两个正方形的边界线不能直接位于标记之上。
定义一种摆放方式的beauty值为正方形面积的乘积,你需要求出所有合法的摆放方式的beauty 值之和。

思路:

这道题要用到一个非常巧妙 (根本就想不到) 的模型转换:
假设我们共放置了 k 个正方形,从左到右边长依次为 a 1 , a 2 , . . . , a k a_1, a_2, . . . , a_k a1,a2,...,ak,依次枚举 a i a_i ai再把它们乘起来显然是不行的。所以,不妨将这种枚举边长类问题转化为计数类问题,也就是说,对于边长为 a 1 , a 2 , . . . , a k a_1, a_2, . . . , a_k a1,a2,...,ak的一种摆放方式,在等价的计数问题中,我们想要恰好将其统计 a 1 2 a 2 2 . . . a k 2 a_1^2a_2^2 . . . a_k^2 a12a22...ak2次。
所以,我们可以将题目转化为如下等价的另一道题:
你有n个空格(从1~ n编号),空格边缘有n+1个摆放隔板的位置(从0~n编号),其中0号位置和n号位置必须放隔板,而有标记的位置不能放隔板,在每两个隔板之间的空格上放置一红一蓝两个小球,同一个格子上可以放两个小球。求放置隔板和放置小球的方案数。
容易发现,这样的转换是等价的,原因如下:
假设摆放隔板的方案已经确定,某两个隔板之间空格个数为 a i a_i ai,那么红色和蓝色小球都分别有 a i a_i ai种放置的方式,合起来看就有 a i 2 a_i^2 ai2种放置的方式,所以当一种摆放隔板的方式已经确定的情况下,摆放小球的方案恰有 a 1 2 a 2 2 . . . a k 2 a_1^2a_2^2 . . . a_k^2 a12a22...ak2种,也就是说,我们将这种方案恰好统计了 a 1 2 a 2 2 . . . a k 2 a_1^2a_2^2 . . . a_k^2 a12a22...ak2次。所以,这样的转换是等价的。
好了,接下来考虑这道新诞生的题目该怎么做。
。。。。
计数类问题,很容易想到的当然是dp啦。
定义 d p [ i ] [ j = 0 , 1 , 2 ] dp[i][ j =0,1,2] dp[i][j=0,1,2]表示前i个空格已确定,在上一个隔板和这一个空格间已经放 j j j个小球的方案数。
转移:
如果第 i − 1 i-1 i1个空格和第 i i i个空格之间不放隔板。
d p [ i ] [ 0 ] = d p [ i − 1 ] [ 0 ] ; dp[i][0]=dp[i-1][0]; dp[i][0]=dp[i1][0];
d p [ i ] [ 1 ] = d p [ i − 1 ] [ 1 ] ( 第 i 个 位 置 不 放 小 球 ) + d p [ i − 1 ] [ 0 ] ( 第 i 个 位 置 放 一 个 小 球 ) dp[i][1]=dp[i-1][1](第i个位置不放小球)+dp[i-1][0](第i个位置放一个小球) dp[i][1]=dp[i1][1](i)+dp[i1][0](i)
d p [ i ] [ 2 ] = d p [ i − 1 ] [ 0 ] ( 第 i 个 位 置 放 两 个 小 球 ) + d p [ i − 1 ] [ 1 ] ∗ 2 ( 第 i 个 位 置 放 一 个 小 球 , 有 红 蓝 和 蓝 红 两 种 方 式 ) + d p [ i − 1 ] [ 2 ] ( 第 i 个 位 置 不 放 小 球 ) dp[i][2]=dp[i-1][0](第i个位置放两个小球)+dp[i-1][1]*2(第i个位置放一个小球,有红蓝和蓝红两种方式)+dp[i-1][2](第i个位置不放小球) dp[i][2]=dp[i1][0](i)+dp[i1][1]2(i)+dp[i1][2](i)
如果第 i − 1 i-1 i1个空格和第 i i i个空格之间放隔板。由于两个隔板间必须有两个小球,所以只能从 d p [ i − 1 ] [ 2 ] dp[i-1][2] dp[i1][2]转移。
d p [ i ] [ 0 ] + = d p [ i − 1 ] [ 2 ] dp[i][0]+=dp[i-1][2] dp[i][0]+=dp[i1][2]
d p [ i ] [ 1 ] + = d p [ i − 1 ] [ 2 ] dp[i][1]+=dp[i-1][2] dp[i][1]+=dp[i1][2]
d p [ i ] [ 2 ] + = d p [ i − 1 ] [ 2 ] dp[i][2]+=dp[i-1][2] dp[i][2]+=dp[i1][2]
于是数据范围在 1 0 5 10^5 105内的就解决了,完整代码如下:

void task1()
{
	static int dp[100010][3];
	dp[0][0]=1;
	for(int i=1;i<=m;i++)
		vis[x[i]]=1;
	for(int i=1;i<=n;i++)
	{
		dp[i][0]=dp[i-1][0];
		dp[i][1]=(dp[i-1][0]+dp[i-1][1])%MO;
		dp[i][2]=((dp[i-1][0]+dp[i-1][1])%MO+(dp[i-1][1]+dp[i-1][2])%MO)%MO;
		if(!vis[i-1])
		{
			dp[i][0]=(dp[i][0]+dp[i-1][2])%MO;
			dp[i][1]=(dp[i][1]+dp[i-1][2])%MO;
			dp[i][2]=(dp[i][2]+dp[i-1][2])%MO;
		}
	}
	printf("%d\n",dp[n][2]);
}

由于 n ≤ 1 0 9 n≤10^9 n109,所以用矩阵乘法优化。可以放隔板的位置,也就是两个标记之间的转移都是一样的,唯一不同的只是有标记的位置。
构造初始矩阵A,表示如果不能放隔板的转移:
A = 1 2 1 0 1 1 0 0 1 A= \begin{matrix} 1 &amp; 2 &amp; 1 \\ 0 &amp; 1 &amp; 1 \\ 0 &amp; 0 &amp; 1 \end{matrix} A=100210111
代表含义:
d p [ i ] [ 2 ] d p [ i ] [ 1 ] d p [ i ] [ 0 ] = A ∗ d p [ i − 1 ] [ 2 ] d p [ i − 1 ] [ 1 ] d p [ i − 1 ] [ 0 ] \begin{matrix} dp[i][2] \\ dp[i][1] \\ dp[i][0] \end{matrix}=A*\begin{matrix} dp[i-1][2] \\ dp[i-1][1] \\ dp[i-1][0] \end{matrix} dp[i][2]dp[i][1]dp[i][0]=Adp[i1][2]dp[i1][1]dp[i1][0]
构造初始矩阵C,表示如果放了隔板的转移:
C = 2 2 1 1 1 1 1 0 1 C= \begin{matrix} 2 &amp; 2 &amp; 1 \\ 1 &amp; 1 &amp; 1 \\ 1 &amp; 0 &amp; 1 \end{matrix} C=211210111
代表含义:
d p [ i ] [ 2 ] d p [ i ] [ 1 ] d p [ i ] [ 0 ] = C ∗ d p [ i − 1 ] [ 2 ] d p [ i − 1 ] [ 1 ] d p [ i − 1 ] [ 0 ] \begin{matrix} dp[i][2] \\ dp[i][1] \\ dp[i][0] \end{matrix}=C*\begin{matrix} dp[i-1][2] \\ dp[i-1][1] \\ dp[i-1][0] \end{matrix} dp[i][2]dp[i][1]dp[i][0]=Cdp[i1][2]dp[i1][1]dp[i1][0]
由于两个标记之间的转移都属于放隔板的转移,所以此处用矩阵快速幂优化。
时间复杂度: O ( m l o g n ) O(mlogn) O(mlogn)

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXN 100010
#define MO 1000000007
#define LL long long
int x[MAXN],n,m;
bool vis[MAXN];
void task1()
{
	static int dp[100010][3];
	dp[0][0]=1;
	for(int i=1;i<=m;i++)
		vis[x[i]]=1;
	for(int i=1;i<=n;i++)
	{
		dp[i][0]=dp[i-1][0];
		dp[i][1]=(dp[i-1][0]+dp[i-1][1])%MO;
		dp[i][2]=((dp[i-1][0]+dp[i-1][1])%MO+(dp[i-1][1]+dp[i-1][2])%MO)%MO;
		if(!vis[i-1])
		{
			dp[i][0]=(dp[i][0]+dp[i-1][2])%MO;
			dp[i][1]=(dp[i][1]+dp[i-1][2])%MO;
			dp[i][2]=(dp[i][2]+dp[i-1][2])%MO;
		}
	}
	printf("%d\n",dp[n][2]);
}
struct node
{
	LL a[3][3];
}A,C,a,b,c,tmp;
void Mul(node &ret,node X,node Y)
{
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			tmp.a[i][j]=0;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			for(int k=0;k<3;k++)
				tmp.a[i][j]=(tmp.a[i][j]+X.a[i][k]*Y.a[k][j]%MO)%MO;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			ret.a[i][j]=tmp.a[i][j];
}
void Pow(int X)
{
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			c.a[i][j]=C.a[i][j];
	while(X)
	{
		if(X&1) Mul(a,a,c);
		Mul(c,c,c);
		X>>=1;
	}
}
int f[3],g[3];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
		scanf("%d",&x[i]);
	memset(A.a,0,sizeof A.a);
	memset(C.a,0,sizeof C.a);
	A.a[0][0]=A.a[1][1]=A.a[2][2]=A.a[1][2]=A.a[0][2]=1;
	A.a[0][1]=2;
	C.a[0][2]=C.a[1][0]=C.a[1][1]=C.a[1][2]=C.a[2][0]=C.a[2][2]=1;
	C.a[0][0]=C.a[0][1]=2;
	f[0]=1;
	x[++m]=n;
	for(int l=1,pos=0;l<=m;l++)
	{
		int k=x[l]-pos-1;
		memset(b.a,0,sizeof b.a);
		memset(a.a,0,sizeof a.a);
		for(int i=0;i<3;i++)
			a.a[i][i]=1;
		if(k) Pow(k);
		Mul(b,a,A);
		for(int i=0;i<3;i++)
		{
			g[i]=0;
			for(int j=0;j<3;j++)
				g[i]=(g[i]+f[j]*b.a[j][i]%MO)%MO;
		}
		for(int i=0;i<3;i++)
			f[i]=g[i];
		pos=x[l];
	}
	printf("%d\n",f[2]);
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值