做题笔记,CF 4.12Div2 E CF1511E Colorings and Dominoes(思维+DP)

做题笔记,CF 4.12Div2 E CF1511E Colorings and Dominoes(思维+DP)

题目大意

有一个长得像下面这样的图(好丑 ):
1000 1000 0010 0000 1000\\1000\\0010\\0000 1000100000100000
1.1表示可以放砖,0不能(0,1反过来也无所谓,理解意思就行)
2.而你可以在每个1的位置放红色或蓝色砖
3.你可以在连续竖着的2个红色位置上放一块骨牌,或者连续横着的两个蓝色位置上放一块骨牌
4.显然,有多少的1,就有2的多少次方的染色方案种数
5.你的任务是对于每种染色方案,记录其最大放骨牌数,对所有染色方案加和
6.图的长宽的乘积小于3e5

做法

1.假如我们当前的染色方案确定的话,我们每个位置只能放横着或者竖着的骨牌,受颜色限制,只能是一种。所以对于所有的染色方案,我们可以把他对答案的贡献拆成两部分,一部分是横着的,一部分是竖着的,这样我们就可以每行或者每列分开求
2.我们可以用一个dp数组来存放长度为i的直链所有支持的放牌方案数下的最大骨牌数和
考虑下面这样一行(列同理)
1 1 1
emmm…它大概是不太行,所以 d p [ 1 ] = 0 dp[1]=0 dp[1]=0
那么下面这一行
11 11 11
它只有在都涂蓝的时候可以放一块横的骨牌,所以 d p [ 2 ] = 1 dp[2]=1 dp[2]=1
同理, d p [ 3 ] = 4 dp[3]=4 dp[3]=4
那么对于
11111111111111...111 11111111111111...111 11111111111111...111
我们考虑他的最后2位( a [ i ] 和 a [ i − 1 ] a[i]和a[i-1] a[i]a[i1]
①.假如 a [ i − 1 ] = 红 a[i-1]=红 a[i1]=那么必然i位置不管是蓝红都放不下新的骨牌 d p [ i ] = d p [ i − 2 ] ∗ 2 dp[i]=dp[i-2]*2 dp[i]=dp[i2]2
②.假如 a [ i − 1 ] = 蓝 a[i-1]=蓝 a[i1]= a [ i ] = 蓝 a[i]=蓝 a[i]=,那么不仅新的位置可以贪心的放一块,一定最优,对于之前 i − 2 i-2 i2位置的最优方案,那么每种方案答案加1,由于之前位置的染色方案显然有 2 i − 1 2^{i-1} 2i1种,此时 d p [ i ] = d p [ i − 2 ] + 2 i − 2 dp[i]=dp[i-2]+2^{i-2} dp[i]=dp[i2]+2i2
③.假如 a [ i − 1 ] = 蓝 a[i-1]=蓝 a[i1]= a [ i ] = 红 a[i]=红 a[i]=,那么,我们考虑下面的过程
d p [ i − 1 ] dp[i-1] dp[i1]应该是 i − 1 i-1 i1位置上放蓝和红的和,那么放红时,此时贡献为 d p [ i − 2 ] dp[i-2] dp[i2]
所以放蓝时的贡献(也就是我们现在 d p [ i ] dp[i] dp[i]应该被更新的值)应该是 d p [ i ] = d p [ i − 1 ] − d p [ i − 2 ] dp[i]=dp[i-1]-dp[i-2] dp[i]=dp[i1]dp[i2]
综上,总的更新式为
d p [ i ] = d p [ i − 2 ] ∗ 2 + d p [ i − 1 ] + 2 i − 2 dp[i]=dp[i-2]*2+dp[i-1]+2^{i-2} dp[i]=dp[i2]2+dp[i1]+2i2
以上是一条链对于答案的贡献
3.考虑每条横的直链,假设有总共tot个可染色的点,链的长度为l,那么考虑除了这条链外其他的染色方案,由于这条链的存在,每种方案的答案都会增加 d p [ l ] dp[l] dp[l],显然其他的染色方案有 2 t o t − l 2^{tot-l} 2totl种,所以每条链的贡献为 d p [ l ] ∗ 2 t o t − l dp[l]*2^{tot-l} dp[l]2totl
然后我们统计所有的横链即可
4.竖链道理一样,再竖着求一遍就行了

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int n,m;
vector<int> a[300005];
char s[300005];
ll p[300005];
ll dp[300005];
ll mod=998244353;
ll ans=0;
int tot=0;
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		scanf("%s",s+1);
		for(int j=1;j<=m;j++){
			if(s[j]=='o') a[i].push_back(1),tot++;
			else{
				a[i].push_back(0);
			}
		}
	}
	p[0]=1;
	for(int i=1;i<=n*m;i++){
		p[i]=p[i-1]*2;p[i]%=mod;
	}
	//for(int i=1;i<=n*m;i++) cout<<p[i]<<" ";
	dp[1]=0,dp[2]=1,dp[3]=3;
	for(int i=4;i<=n*m;i++){
		dp[i]=dp[i-1]+2*dp[i-2]+p[i-2];
		dp[i]%=mod;
	}
	for(int i=1;i<=n;i++){
		int now=0;
		for(int j=0;j<m;j++){
			if(a[i][j]) now++;
			else{
				if(now>=2){
					ans+=(dp[now]*p[tot-now])%mod; 
					ans%=mod;
				}
				now=0;
			}
		}
		if(now>=2) ans+=(dp[now]*p[tot-now])%mod;ans%=mod; 
	}
	for(int i=0;i<m;i++){
		int now=0;
		for(int j=1;j<=n;j++){
			if(a[j][i]) now++;
			else{
				if(now>=2){
					ans+=(dp[now]*p[tot-now])%mod; 
					ans%=mod;
				}
				now=0;
			}
		}
		if(now>=2) ans+=(dp[now]*p[tot-now])%mod;ans%=mod; 
	}
	printf("%lld",ans);
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值