そんなテクもあったな。
https://atcoder.jp/contests/abc285/tasks/abc285_h
問題
整数N,Kと長さKの整数列Eが与えられる。
Eは、素数のうち小さい順にK個を使った、ある整数の素因数分解の結果を示すものとする。
積が上記整数となるような、N要素の正整数列を考える。
全要素が平方数でないようなものは何通りか。
解法
各要素、少なくとも1つの素因数で位数が奇数ならよい。
包除原理の要領で考える。
f(i) := N要素中i要素が平方数であり、残りi要素は平方数かどうかわからない
とすると、求める値は
である。
だが、右辺の無限級数の部分は
と書ける。1/(1-x)^Nは、結局累積和をN回取ること、(1+x)^jは、符号を正負入れ替えながら累積和をj回取ること、と考えると、max(E)+1要素の数列を2N回累積和取れば、この分数の部分を計算できる。
int N,K; int E[101010]; const ll mo=1000000007; int dp[10101][10101]; ll comb(ll N_, ll C_) { const int NUM_=400001; static ll fact[NUM_+1],factr[NUM_+1],inv[NUM_+1]; if (fact[0]==0) { inv[1]=fact[0]=factr[0]=1; for (int i=2;i<=NUM_;++i) inv[i] = inv[mo % i] * (mo - mo / i) % mo; for (int i=1;i<=NUM_;++i) fact[i]=fact[i-1]*i%mo, factr[i]=factr[i-1]*inv[i]%mo; } if(C_<0 || C_>N_) return 0; return factr[C_]*fact[N_]%mo*factr[N_-C_]%mo; } void solve() { int i,j,k,l,r,x,y; string s; cin>>N>>K; FOR(i,K) cin>>E[i]; dp[N][0]=1; for(i=N-1;i>=0;i--) { FOR(x,10001) { dp[i][x]=dp[i+1][x]; if(x) (dp[i][x]+=dp[i][x-1])%=mo; } } ll ret=1; FOR(j,K) (ret*=dp[0][E[j]])%=mo; for(i=1;i<=N;i++) { FOR(x,10001) { dp[i][x]=dp[i-1][x]; if(x) (dp[i][x]+=mo-dp[i][x-1])%=mo; } ll pat=comb(N,i); FOR(j,K) (pat*=dp[i][E[j]])%=mo; if(i%2==0) { ret+=pat; } else { ret+=mo-pat; } } cout<<ret%mo<<endl; }
まとめ
あえてNTTを使わせない制約ってのがいいね。