省赛题,不但是英文的而且有一定的难度。之前一直陷入冥思苦想,结果不知道为什么今天再看到的时候突然灵光一现,一下子就20分钟一遍秒杀了。所以说,对于找方法,找bug这种np问题,运气还是有点重要的。
先分析一下这道题,首先函数就是一种映射,从之前的1到n映射到之后的1到n,首先可以证明这个映射肯定是一一映射,即每个点都映射到一个不同的值,因为如果有多个映射到同一个的话,最后n个数就不可能得到n个结果,所以必须是唯一的,而且如果把映射作为一个有向边的话可以发现这个图肯定是若干个环组成的(有可能有自环),因为你必须最后回到自己,而且都是一一映射,所以只能是若干个环,那么这些环应该满足什么条件呢,因为走k次肯定能走回来所以环的边数肯定要整除k,那么这个问题就变成了一个划分和排列组合的问题,但是你会发现这样搞的话非常复杂,肯定会超时,所以一时陷入了瓶颈。
不过再一想发现这个题是可以用dp解决的。首先因为环需要整除k,k+1和k基本没有任何关系,所以k是不需要变化的,那么变化的就只能是n,考虑新增一个人的时候会新增多少种情况,这个时候发现只要考虑他所在的环即可,因为这个环肯定是全新的,所以每个这个人所处的不同环都考虑一遍的话他们的和就是结果了,这时考虑每一种情况,加入第i个人在一个人数为m的环,那么为这个环配上m-1个人从i-1个人里选择,另外如果把第i个人视为开头的话那么这些人的排列的顺序正好就是这么多人组成的不同环的数量,所以可以直接视为A(i - 1 , m - 1),然后剩下i-m个人的数量,这个之前已经求出来了,两个一乘就ok了。
因为本身不大,所以分解k的话直接用根号k的即可,另外预处理就不多说了。
贴代码
# include <stdio.h> # include <string.h> # include <vector> # include <algorithm> using namespace std; typedef long long ll; const int MAX_N = 2e4; const int Mod = 1e9 + 7; ll dp[MAX_N + 1]; ll jie[MAX_N + 1], ni[MAX_N + 1]; int N, K, T; vector<int> G; ll po(ll a , ll b , ll mo) { ll ans = 1; while(b) { if(b & 1) ans = ans * a % mo; b >>= 1; a = a * a % mo; } return ans; } inline ll C(int n , int m) { ll j = jie[n] * ni[m] % Mod; j = j * ni[n - m] % Mod; return j; } inline ll A(int n , int m) { return jie[n] * ni[n - m] % Mod; } int main() { int i, j; jie[0] = jie[1] = 1; ni[0] = ni[1] = 1; for(i = 2 ; i <= MAX_N ; i++) jie[i] = jie[i - 1] * i % Mod; for(i = 2 ; i <= MAX_N ; i++) ni[i] = po(jie[i] , Mod - 2 , Mod); scanf("%d", &T); while(T--) { scanf("%d %d", &N, &K); memset(dp , 0 , sizeof(dp)); dp[0] = 1; G.clear(); for(i = 1 ; i * i <= K ; i++) { if(K % i == 0) { G.push_back(i); if(K / i != i) G.push_back(K / i); } } sort(G.begin() , G.end()); for(i = 1 ; i <= N ; i++) { for(j = 0 ; j < G.size() ; j++) { if(G[j] > i) break; dp[i] += dp[i - G[j]] * A(i - 1 , G[j] - 1); dp[i] %= Mod; } } printf("%lld\n", dp[N]); } return 0; }