迷恋 题解

题目

题目描述

  • 这不是正义的义务,而是作为正义的盟友!
  • 不是盟友,而是正义本身,Onii-chan!

这一次,火姐妹 - 凯伦和Tsukihi - 正在前往他们从未到达的地方 - 水包围的岛屿!
有三种群岛,分别有三种颜色红色,蓝色和紫色的怪物。每种颜色的岛屿分别有a,b,c个。
桥梁在一些(可能全部或者部分)岛屿之间建成。桥梁双向连接两个不同的岛屿并具有长度1.对于任何两个相同颜色的岛屿,他们彼此不可达,或者它们之间的最短距离至少为3。这是为了防止同一种颜色的怪物种群相互蔓延。
火姐妹已经准备好了,但他们也想测试你的勇气。你在这里找出在限制下建立所有桥梁的不同方式的数量,并给出答案模数998244353(即答案要取余数)。只要有一对建桥的岛屿不一样,那么两种方法就是不一样的。
输入
第一行也仅包含三个空格分隔的整数a,b和c(1≤a,b,c≤5 000) - 红色,蓝色和紫色怪物的岛数。
输出
输出一行包含整数 - 构建桥接的不同方式的数量,模数为998244353。
样例输入1
1 1 1
样例输出1
8
样例输入2
1 2 2
样例输出2
63
样例输入3
1 3 5
样例输出3
3264
提示
注意
在第一个例子中,有3个桥可以建立,没有桥梁的设置违反了限制。所以答案是2^3 = 8。
在第二个例子中,下图中的上两个结构是有效的,下两个是无效的。
数据范围:
对于 \(50\%\) 的数据,\(a,b,c\) 都小于等于5。
对于 \(100\%\) 的数据 \(a,b,c\) 小于等于5000

题目解析

我们发现有三种岛屿,但是我们可以先分析两种。不难发现,任意一个岛屿都不能连接与自己颜色相同的岛屿或者是两个同种颜色的岛屿。
然后发现如果有三种岛屿,这些条件还是成立的。

那么我们就可以设 \(\operatorname{f}(i,j)\) 为一种岛屿选 \(i\) 个,另一种岛屿选 \(j\) 个的情况。
答案就是 \(\operatorname{f}(a,b)\times\operatorname{f}(b,c)\times\operatorname{f}(a,c)\)
接下来考虑如何得到 \(\operatorname{f}(i,j)\)

step1

考虑DP方程式转移。
不难得出 \(\operatorname{f}(0,i)=\operatorname{f}(i,0)=1\) ,显然这些情况下不能连一条边。
考虑 \(\operatorname{f}(i,j)\),我们假设加入了一个第一种岛屿。
如果我们没有将这个岛屿连进去,那么总共有 \(\operatorname{f}(i-1,j)\) 种可能。
如果这个岛屿连进去了,那么一定有 \(j\) 种连法,但是同时连到的第二种岛屿就不能和其他岛屿连了,那么就是 \(\operatorname{f}(i-1,j-1)*j\) 种可能。
综上 \(\operatorname{f}(i,j)=\operatorname{f}(i-1,j)+\operatorname{f}(i-1,j-1)*j\)
代码

//O(N^2)
#include<cstdio>
#define maxn 5039 
#define MOD 998244353
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
//#define debug
typedef long long ll;//long long
typedef int Type;
inline Type read(){
	Type sum=0;
	int flag=0;
	char c=getchar();
	while((c<'0'||c>'9')&&c!='-') c=getchar();
	if(c=='-') c=getchar(),flag=1;
	while('0'<=c&&c<='9'){
		sum=(sum<<1)+(sum<<3)+(c^48);
		c=getchar();
	}
	if(flag) return -sum;
	return sum;
}
ll f[maxn][maxn];
int a,b,c;
int n;
int main(){
    //freopen("love.in","r",stdin);
    //freopen("love.out","w",stdout);
    a=read(); b=read(); c=read();
    n=max(a,max(b,c));
    for(int i=0;i<=n;i++)
        f[0][i]=f[i][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            f[i][j]=((f[i-1][j-1]*j)%MOD+f[i-1][j])%MOD;
    #ifdef debug
    for(int i=0;i<=n;i++){
        for(int j=0;j<=n;j++)
    	    printf("%6lld ",f[i][j]);
    	putchar('\n');
	}
	#endif
    printf("%lld",((f[a][b]*f[a][c])%MOD*f[b][c])%MOD);
	return 0;
}

step2

step1的方法已经可以通过了,但是我们发现有些毒瘤出题人把 \(a,b,c\) 扩大到了 \(20000000\) (两千万)
我们该怎么做呢?
显然我们还是设 \(\operatorname{f}(i,j)\) 为一种岛屿选 \(i\) 个,另一种岛屿选 \(j\) 个的情况,不妨设 \(i<j\)
我们发现在第一种岛屿和第二种岛屿都只能连一条边,我们可以枚举连边的数量。
然后我们固定第一种的方法不动,然后我们会发现对另一种进行排列就可以做到不重不漏了。

\[\operatorname{f}(i,j)=\sum^j_{k=0}C^k_i\times C^k_j\times P^k_k \]

展开得

\[\operatorname{f}(i,j)=\sum^j_{k=0}\frac{i!\times j!}{(i-k)!\times (j-k)! \times k!} \]

我们只需要预处理出 \(i!\) 就可以 \(O(\max(a,b,c))\) 计算了。
由于要取模,所以还要预处理出 \(i!^{-1}\) ,只需要阶乘逆元线性求即可,不会的可以点这里(推销博客)
代码:

//O(N)
#include<cstdio>
#define MOD 998244353
#define maxn 5039
using namespace std;
//#define debug
typedef long long ll;
typedef int Type;
inline int max(int x,int y){ return x>y?x:y; }
inline Type read(){
	Type sum=0;
	int flag=0;
	char c=getchar();
	while((c<'0'||c>'9')&&c!='-') c=getchar();
	if(c=='-') c=getchar(),flag=1;
	while('0'<=c&&c<='9'){
		sum=(sum<<1)+(sum<<3)+(c^48);
		c=getchar();
	}
	if(flag) return -sum;
	return sum;
}
int a,b,c,n;
ll inv[maxn],f[maxn];
ll pow(ll x,ll y){
	ll res=1,tmp=x;
	while(y){
		if(y&1) res=res*tmp%MOD;
		tmp=tmp*tmp%MOD;
		y>>=1;
	}
	return res;
}
void pre(){
	f[0]=1;
	for(int i=1;i<=n;i++)
	    f[i]=f[i-1]*i%MOD;
	inv[n]=pow(f[n],MOD-2);
	for(int i=n-1;i>=0;i--)
	    inv[i]=inv[i+1]*(i+1)%MOD;
	return;
}
#define swap(x,y) x^=y^=x^=y;
inline ll js(int i,int j){
	ll res=0;
	if(i<j) swap(i,j);//i>=j
	for(int k=0;k<=j;k++)
		res=(res+f[i]*f[j]%MOD *inv[i-k]%MOD *inv[j-k]%MOD *inv[k]%MOD )%MOD;
	return res;
}
int main(){
    freopen("love.in","r",stdin);
    freopen("love.out","w",stdout);
    scanf("%d%d%d",&a,&b,&c);
    n=max(a,max(b,c));
    pre();
    printf("%lld",js(a,b)*js(a,c)%MOD *js(b,c)%MOD );
	return 0;
}

posted @ 2021-04-22 20:50  jiangtaizhe001  阅读(69)  评论(2编辑  收藏  举报