TopCoder SRM 472 Div1 Medium TwoSidedCards
問題
n枚のカードがある。
i枚目のカードの表にはtaro[i]の数字が書かれており、裏にはhanako[i]の数字が書かれている。
これらのカードを全て一列に並べたときに現れる数字の列は何通りあるか。
制約条件
n≦50
taro[i],hanako[i]は1〜nの整数が一度ずつ現れる。
方針
頂点がn個のグラフで、以下のようなものを考える。
表がaのカードの裏に書かれている数字をbとするとき、a番の頂点からb番の頂点に辺がある。
この、グラフはいくつかのサイクルから成り立っている。
それぞれのサイクルごとに、カードの並べ方が何通りあるかは独立に数えられるので、全て独立に数えて掛け算すれば良い。
サイクルごとにカードの並べ方が何通りあるかは、サイクルの長さだけに依存することがわかる。
サイクルの「辺ひとつにどちらの向きを付けるか」に対して「カード一枚の裏表」が一対一に対応している。
同じ数字がi回出てくるような裏表の向き付けが何通りあるかを求めたい。
これは以下のような状態を取るDPにより求めることができる。
dp[i枚まで見た][いままでの同じ数字の個数][最後のカードが裏か][最初のカードが裏か]
一回も数字がだぶらないようなカードの使い方は、最初のカードを裏にする場合と、表にする場合で、数字の組が同じであるのに、2通りにえられているので1をひく。
後はこれを足し算すれば同じ数字がi回出てくるときのカードの場合の数が求まる。
ソースコード
int n,nxt[50]; bool v[50]; int rec(int c){ v[c]=1; return v[nxt[c]]?1:1+rec(nxt[c]); } ll extgcd(ll a,ll b,ll &x,ll &y){ ll d=1; if(b!=0){ d=extgcd(b,a%b,y,x); y-=a/b*x; } else x=1, y=0; return d; } int inv(int n){ ll x,y; extgcd(n,mod,x,y); return (x%mod+mod)%mod; } int C(int n,int k){ ll res=1; rep(i,k)res=res*(n-i)%mod,res=res*inv(i+1)%mod; return (int)res; } int dp[51][51][2][2],ans[51]; int calc(int m){ memset(dp,0,sizeof(dp)); dp[0][0][1][1]=dp[0][0][0][0]=1; rep(i,m-1)rep(j,i+1)rep(k,2){ (dp[i+1][j+1][0][k]+=dp[i][j][1][k])%=mod; (dp[i+1][j][1][k]+=dp[i][j][0][k])%=mod; (dp[i+1][j][0][k]+=dp[i][j][0][k])%=mod; (dp[i+1][j][1][k]+=dp[i][j][1][k])%=mod; } memset(ans,0,sizeof(ans)); rep(i,m+1){ (ans[i]+=dp[m-1][i][1][1])%=mod; (ans[i]+=dp[m-1][i][0][1])%=mod; (ans[i]+=dp[m-1][i][0][0])%=mod; (ans[i+1]+=dp[m-1][i][1][0])%=mod; } ans[0]--; ll res=0; rep(i,m+1)if(ans[i]){ ll tmp=ans[i]; int rest=m; rep(j,i)tmp=tmp*C(rest,2)%mod, rest-=2; rep(i,rest)tmp=tmp*(i+1)%mod; res+=tmp; } return res%mod; } class TwoSidedCards { public: int theCount(vector <int> taro, vector <int> hanako) { n=taro.size(); vector<pi> p; rep(i,n)p.pb(mp(taro[i],hanako[i])); sort(all(p)); rep(i,n)nxt[i]=p[i].second-1; ll ans=1; memset(v,0,sizeof(v)); int rest=n; rep(i,n)if(!v[i]){ int m=rec(i); ans=ans*C(rest,m)%mod*calc(m)%mod; rest-=m; } return (int)ans; } };