381 words
2 分钟minutes
Cards (ABC 247 F)

AtCoder - abc247_f

题目大意#

你有NN张卡片,每张卡有正反两面,正面有一个数字PiP_i,反面有一个数字QiQ_i,数列PPQQ都是(1,2,,N)(1,2,\dots,N)的全排列,问有多少种选择方法使得NN个数都至少一次出现在被选中的牌上,答案对998244353998244353取模。(1N2×105)(1\le N\le2\times 10^5)

思路#

首先,我们建一张图,把每张牌正反的两个数连起来,而由于PPQQ是全排列,每个数都只会出现两次,每个点的度都是22,所以我们建出来的图会由很多个环构成。然后我们先来考虑一个简单一点的问题:1,2,,m1,2,\dots,mmm个数中相邻的两个数至少得选一个的方案数是多少。就是分类讨论mm是否选,这样的话,设答案为f(m)f(m),则满足f(1)=2,f(2)=3,,f(m)=f(m1)+f(m2)f(1)=2,f(2)=3,\dots,f(m)=f(m-1)+f(m-2)。然后现在我们要求的是1,2,,m1,2,\dots,mmm个数围成一个圈时连着一个点的两条边至少得选一条的方案数,设答案为g(m)g(m),分类讨论一下11mm是否相连,就能得到g(1)=1,g(2)=3,,g(m)=f(m1)+f(m3)g(1)=1,g(2)=3,\dots,g(m)=f(m-1)+f(m-3),观察一下可以发现g(m)=Lmg(m)=L_mLL为卢卡斯数列。这样,我们只要把每个环的答案乘起来就结束了。

代码#

#include<bits/stdc++.h>
using namespace std;
long long a[200005];
long long mod=998244353;
int p[200005];
int q[200005];
int fa[200005];
int siz[200005];
int vis[200005];
int findd(int x)
{
return fa[x]==x?x:(fa[x]=findd(fa[x]));
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&p[i]);
for(int i=1;i<=n;i++)scanf("%d",&q[i]);
for(int i=1;i<=n;i++){fa[i]=i;siz[i]=1;}
for(int i=1;i<=n;i++)if(findd(p[i])!=findd(q[i]))
{
siz[findd(q[i])]+=siz[findd(p[i])];
fa[findd(p[i])]=findd(q[i]);
}
a[0]=2;
a[1]=1;
for(int i=2;i<=n;i++)a[i]=(a[i-1]+a[i-2])%mod;
long long ans=1;
for(int i=1;i<=n;i++)if(!vis[findd(i)])
{
ans=ans*a[siz[findd(i)]]%mod;
vis[findd(i)]=1;
}
printf("%lld\n",ans);
return 0;
}
Cards (ABC 247 F)
https://jerryblack.vercel.app/posts/cards-abc247f/
作者Author
逸少( ̄^ ̄)ゞJerry Black
发布于Published at
2022-04-29
许可协议License
CC BY-NC-SA 4.0