gmoj 7047. 2021.04.07【2021省赛模拟】染色(coloring) 题解

地址: https://gmoj.net/senior/#main/show/7047

题解

20 20 20 分的做法枚举哪些行、对角线染色,然后判重。

但是因为前 20 % 20\% 20% 的数据中有一个 n = 8 , m = 9 n=8,m=9 n=8,m=9 的, 2 n m 2^{nm} 2nm 超过了 unsigned long long 的范围,很多人都只拿了 15 15 15 分。然而如果你用的是 long long ,就能把这 5 5 5 分给拿了。

如果想拿更高的分数会发现这题的状态很难表示,无法用常见的状压DP的套路。因此需要转化题意。

考虑一些对角线不染色,那么这些对角线每条都会使一些行空出一个格子来。发现一条对角线覆盖的行是连续的,因此可以把一条对角线表示为一个区间 [ l i , r i ] [l_i,r_i] [li,ri] 。而其它的对角线都是被染色了的,因此不用考虑它们,只需要考虑 ∪ [ l i , r i ] \cup [l_i,r_i] [li,ri] 所覆盖的行是否被染色即可。但要注意,不能把某条被不染色的对角线所覆盖的行全部染色。

那么问题就转化为:有 n + m − 1 n+m-1 n+m1 个区间,你可以选择其中的一些,然后在它们的并里选择一些元素进行染色,要求不能有某个区间被完全染色,问方案数。

f i , j f_{i,j} fi,j 表示现在已经考虑完前 i i i 条对角线是否染色,其中第 i i i 条对角线没有被染色,且第 i i i 条对角线中最后连续 j j j 个元素都被染色(也就是最后一个没有染色的元素是倒数第 j + 1 j+1 j+1 个)的方案,令 g i = ∑ j = 0 r i − l i f i , j g_i=\sum_{j=0}^{r_i-l_i}f_{i,j} gi=j=0rilifi,j

考虑枚举上一个没有染色的区间 k k k 来转移,分三种情况讨论。

  1. 区间 i i i 和区间 k k k 不相交(即 r k < l i r_k<l_i rk<li )时:
    那么这时第 i i i 个区间的倒数第 j + 1 j+1 j+1 个元素以前的位置都可以选择染色或不染色,因此 f i , j + = 2 r i − l i − j g k f_{i,j}+=2^{r_i-l_i-j}g_k fi,j+=2rilijgk

  2. 区间 i i i 和区间 k k k 相交(即 r k ≥ l i r_k\ge l_i rkli ),且第 i i i 个区间的倒数第 j + 1 j+1 j+1 个元素在第 k k k 个区间里(即 r k ≥ r i − j r_k\ge r_i-j rkrij )时:
    那么这时第 k k k 个区间的选择情况就被固定了, f i , j + = f k , r k − r i + j f_{i,j}+=f_{k,r_k-r_i+j} fi,j+=fk,rkri+j

  3. 区间 i i i 和区间 k k k 相交(即 r k ≥ l i r_k\ge l_i rkli ),且第 i i i 个区间的倒数第 j + 1 j+1 j+1 个元素不在第 k k k 个区间里(即 r k < r i − j r_k< r_i-j rk<rij )时:
    那么这时 [ r k + 1 , r i − j ) [r_k+1,r_i-j) [rk+1,rij) 中的元素就可以被随便选,因为第 i i i 个区间的倒数第 j + 1 j+1 j+1 个元素没有被选,因此第 k k k 个区间可以随便选, f i , j + = 2 r i − r k − j − 1 g k f_{i,j}+=2^{r_i-r_k-j-1}g_k fi,j+=2rirkj1gk

时间复杂度 O ( n 3 ) O(n^3) O(n3)

由于模数不一定是质数, 2 − 1 2^{-1} 21 不一定存在,因此这个方法不能优化为 O ( n 2 ) O(n^2) O(n2)


代码

#include<cstdio>
using namespace std;
#define fo(i,l,r) for(i=l;i<=r;++i)
#define N 1005
int f[N][N],g[N],l[N],r[N],pow2[N],P;
inline void add(int &x,int y){x+=y;if(x>=P) x-=P;}
inline int mymin(int x,int y){return x<y?x:y;}
int main()
{
	freopen("coloring.in","r",stdin);
	freopen("coloring.out","w",stdout);
	register int i,j,k;
	int n,m,s,ans=0;
	scanf("%d%d%d",&n,&m,&P);
	s=n+m-1,pow2[0]=1;
	fo(i,1,1000) pow2[i]=2LL*pow2[i-1]%P;
	fo(i,1,m) l[i]=1,r[i]=mymin(i,n);
	for(i=1;i<n;++i) l[m+i]=i+1,r[m+i]=mymin(m+i,n);
	f[0][0]=g[0]=f[1][0]=g[1]=1;
	fo(i,2,s) fo(j,0,r[i]-l[i])
	{
		fo(k,0,i-1)
		{
			if(r[k]<l[i]) add(f[i][j],1LL*g[k]*pow2[r[i]-l[i]-j]%P);
			else if(r[k]>=r[i]-j) add(f[i][j],f[k][r[k]+j-r[i]]);
			else add(f[i][j],1LL*g[k]*pow2[r[i]-r[k]-j-1]%P);
		}
		add(g[i],f[i][j]);
	}
	fo(i,0,s) add(ans,g[i]);
	printf("%d\n",ans);
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值