码迷,mamicode.com
首页 > 其他好文 > 详细

AT3558 Modern Painting

时间:2021-03-06 14:40:07      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:输出   枚举   方案   组合数   scanf   统计   inline   一个   解决   

首先需要明确的是,我们要求的是不同的染色方案数!!!

然后我们不妨来考虑一下一个只有向右和向下的特殊情况。

然后你会发现他一定满足有一个向右下延伸的颜色的分界线,为什么呢?

我们首先比较显然的,若图上有 \(n\) 个向右的, \(m\) 个向下的,我们可以将其简化为一个 \(n\times m\) 的矩形。

然后你首先对于 \((1,1)\) 这个格子,你发现对于他的颜色,实际上是决定了是第一行的机器人先走还是第一列的机器人先走,如果是第一行的先走,你就可以往右一格,继续考虑是第一行的先走还是第二列的先走……如此类推,你发现最终的一种涂色状态必然对应一种由 \((1,1)\) 走到 \((n+1,m+1)\) 的方案。

为什么是 \((n+1,m+1)\) 呢?实际上我们颜色的比较到达第 \(n+1\) 行或者第 \(m+1\) 列是就可以结束了,相当于是要统计从 \((1,1)\) 到第 \(n+1\) 行和第 \(m+1\) 列的总方案数,此数值正好等于到达 \((n+1,m+1)\) 的方案数。

以上,我们相当于证明了一个只有 \(n\) 个向右的和 \(m\) 个向下的位置的方案数为 \(\binom{n+m}{n}\)

我们需要将这个东西扩展一下。我们考虑再加一个向上的方向该如何计算呢?

沿用我们上面的思路,相当于一个位置我们可能要考虑三个方向的先后,好像不好办了。

但是如果你发现如果存在一条横线将其横向划分开,那么他就被转化为了我们上述的子问题了。

我们不妨设向右的有 \(n\) 个,向下的有 \(m\) 个,向上的有 \(k\) 个,我们钦定选择一个横向的连上,然后同时强制上半部分不能同时出现横向连满的,下半部分却可以,那么式子是:

\[f(n,m,k)=\sum_{i=1}^n\binom{i+m-2}{m-1}\binom{n-i+k}{k}\=\binom{n+m+k-1}{n-1} \]

目前为止,我们已经解决了有三个方向的问题,式子如上。

其实你可以将两个式子分别看成你要走的两个方向的方案数,然后这个 \(\sum\) 实际上可以看成是枚举你在什么位置向右(或向上,总之就是不是和 \(i\) 在同一个维度上的方向)走一步,然后整个式子就变成了一个组合数。

然后考虑四个方向的怎么办,有一种方法就是枚举如何将整个矩形分解成三个方向的,复杂度应该是 \(O(n^2)\) 的,然后你考虑前缀和优化一下就可以了吧。

我来试一试。


然后你发现,如果这么做的话有很多的样例是过不了的,一下有几个需要特判的地方。

  1. 如果没有机器人,输出 \(1\)
  2. 考虑横竖两个方向都可以将整个矩形分成两个只有三个方向的矩形。
  3. 在考虑三个方向的问题时,如果 \(n\)\(0\) ,就不能用公式计算了,具体如何计算可以自己思考。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
const int MOD=998244353;
int n,m,fac[N<<2],ifac[N<<2];
int a[N],b[N],c[N],d[N];//r,l,d,u
int f[N],res=0;
int ksm(int x,int k){
	int res=1;
	for(;k;k>>=1,x=x*x%MOD)
	if(k&1) res=res*x%MOD;
	return res;
}
int cal(int n,int m){
	if(n<0||m<0||n<m) return 1;
	return fac[n]*ifac[m]%MOD*ifac[n-m]%MOD;
}
void solve(){
	memset(f,0,sizeof(f));
	int cnt1=0,cnt2=0,cnt3=0;
	for(int i=1;i<=n;++i) cnt1+=a[i];
	for(int i=1;i<=n;++i) cnt2+=b[i];
	if(!cnt1&&!cnt2){
		for(int i=1;i<=m;++i) cnt3+=(c[i]&&d[i]);
		res+=ksm(2,cnt3),res%=MOD;
		return ;
	}
	if(!cnt2){
		swap(cnt1,cnt2);
		for(int i=1;i<=n;++i) swap(a[i],b[i]);
		for(int i=1;i<=(m>>1);++i) swap(c[i],c[m-i+1]);
		for(int i=1;i<=(m>>1);++i) swap(d[i],d[m-i+1]);
	}
	if(cnt1){
		for(int i=1;i<=m;++i){
			if(c[i]||d[i]) f[i]=cal(cnt1+cnt3-1,cnt1-1);
			cnt3+=c[i]+d[i];
		}
		for(int i=1;i<=m;++i){
			f[i]+=f[i-1],f[i]%=MOD;
			if(c[i]&&d[i]) f[i]<<=1,f[i]%=MOD;
		}
	}
	else{
		for(int i=1;i<=m;++i){
			cnt3+=(c[i]&&d[i]);
			if(c[i]||d[i]) f[i]=ksm(2,cnt3);
		}
	}
	cnt3=0;
	for(int i=m;i>=1;--i){
		if(c[i]||d[i]) res+=f[i]*cal(cnt2+cnt3-1,cnt2-1)%MOD,res%=MOD;
		cnt3+=c[i]+d[i];
	}
}
signed main(){
	cin>>n>>m,fac[0]=ifac[0]=1;
	for(int i=1;i<=n+n+m+m;++i) fac[i]=fac[i-1]*i%MOD;
	for(int i=1;i<=n+n+m+m;++i) ifac[i]=ksm(fac[i],MOD-2);
	int cnt=0;
	for(int i=1;i<=n;++i) scanf("%1lld",&a[i]),cnt+=a[i];
	for(int i=1;i<=n;++i) scanf("%1lld",&b[i]),cnt+=b[i];
	for(int i=1;i<=m;++i) scanf("%1lld",&c[i]),cnt+=c[i];
	for(int i=1;i<=m;++i) scanf("%1lld",&d[i]),cnt+=d[i];
	if(!cnt) return printf("1"),0;
	solve(),swap(n,m),swap(a,d),swap(a,b),swap(a,c),solve();
	return printf("%lld\n",res),0;
}

AT3558 Modern Painting

标签:输出   枚举   方案   组合数   scanf   统计   inline   一个   解决   

原文地址:https://www.cnblogs.com/Point-King/p/14488378.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!