Codeforces997C Sky Full of Stars 【FMT】【组合数】

时间:2023-03-08 17:50:53

题目大意:

一个$n*n$的格子,每个格子由你填色,有三种允许填色的方法,问有一行或者一列相同的方案数。

题目分析:

标题的FMT是我吓人用的。

一行或一列的问题不好解决,转成它的反面,没有一行和一列相同的方案数。

从一个方向入手,比如列,把一列看成一个整体。把颜色看成二进制数,$001$,$010$,$100$。

那么一列构成了一个长度为$3n$的二进制数,$n$列之间互相与出来的结果为$0$。实际我要统计这个东西。

注意到每一列的取法是不能取相同颜色的,所以剔除相同。之后我们得到了每一列可选的情况。

将它做FMT,之后做$n$次方,然后做IFMT,$0$位上的就是答案。用$9^n$减去这个数字就行。

直接做的时间复杂度是$O(n*2^n)$的,我们远不能承受。

但是我们有用的状态却不多,甚至还有规律。比如FMT后的某个位$bit$如果每三位出现两个$1$那么这个的FMT值一定是$0$,然后如果每三位只有$1$个$1$那么该位贡献$1$次,否则贡献$3$次。

然后是IFMT的还原问题,经过观察,不难发现如果某个位$bit$的$1$的个数为奇数,那么对$0$位产生减的影响,否则产生加的影响。

综合上面两个因素,可以利用组合数来统计方案数。值得注意的是如果每三位的1的位置相同那么要提防填充出相同结果。

时间复杂度$O(n)$

代码:

 #include<bits/stdc++.h>
using namespace std; const int maxn = ;
const int mod = ; int n; int f[maxn][],g[maxn][];
int pw3[maxn];
int c[maxn]; int fast_pow(int now,int pw){
int ans = ,dt = now,bit = ;
while(bit <= pw){
if(bit & pw) ans = (1ll*ans*dt)%mod;
bit <<=;dt = (1ll*dt*dt)%mod;
}
return ans;
} void work(){
if(n == ) {puts("");return;}
pw3[] = ;for(int i=;i<=n;i++) pw3[i] = 3ll*pw3[i-]%mod;
c[] = ;
for(int i=;i<=n;i++){
c[i]=(1ll*c[i-]*(n-i+))%mod;
c[i]=(1ll*c[i]*fast_pow(i,mod-))%mod;
}
int sum = fast_pow(pw3[n],n);
f[][] = ; g[][] = (pw3[n]-+mod)%mod;
f[n][] = (pw3[n]-+mod)%mod; g[n][] = ;
for(int i=;i<n;i++){
f[i][] = 3ll*c[i]%mod;
f[i][] = (1ll*pw3[i]*c[i])%mod;
f[i][] -= f[i][]; f[i][] += mod; f[i][] %= mod;
g[i][] = pw3[n-i]; g[i][] = (pw3[n-i]-+mod)%mod;
}
for(int i=;i<=n;i++){
g[i][] = fast_pow(g[i][],n); g[i][] = fast_pow(g[i][],n);
int dr = ((i&)?:-);
sum += dr*(1ll*g[i][]*f[i][])%mod;
if(sum >= mod) sum-=mod; if(sum < ) sum += mod;
sum += dr*(1ll*g[i][]*f[i][])%mod;
if(sum >= mod) sum-=mod; if(sum < ) sum += mod;
}
printf("%d",sum); } int main(){
scanf("%d",&n);
work();
return ;
}