2017多校第一套&&hdu6038 思维 数学
链接 http://acm.hdu.edu.cn/showproblem.php?pid=6038
题意:
给你一个a序列,代表0到n-1的排列;一个b序列代表0到m-1的排列。问你可以找出多少种函数关系f,f的定义域内的i都满足f(i)=b[f(a[i])];
分析:这个主要是找循环节 循环节导致函数有多种情况 找到每段循环节的 取值 种数 相乘起来就是答案
比如说:如果 a 序列是 2 0 1 那么我们可以发现
f[0] = b[f(a[0])] f[0] = b[f(2)]
f[1] = b[f(a[1])] f[1] = b[f(0)]
f[2] = b[f(a[2])] f[2] = b[f(1)]
对于这组数来说,假如我们先指定了f(0)对应的在b中的值,那么根据第2个式子,就可以得出f(1),根据f(1)就又可以得出f(2),最后根据f(2)就可以检验f(0)的值是否正确。
仔细观察左边的柿子 (定义域)i与a[ i ] 是循环的 如果想使上述成立,必须右边的柿子(值域)i与b[ i ] 也存在其约数长度的循环节。
如果不是约数长度的循环节会使上述假设矛盾 自己写一下就知道了 只有被整出才会成立。
每个定义域的循环节都可以与每个值域的循环节匹配(满足红字匹配成功)
那么就是找两个序列的不相交的循环节,对于定义域里面的每一个循环节都找出来有多少种情况。每一个循环节的情况数为它所能匹配的值域循环节的长度 和。每个循环节的情况数乘起来就是答案了
样例一 (1+1)*(1+1)=4; a两个环(长度 2 1) b两个个环(长度 1 1) 2与1 1匹配 1与1 1 匹配
样例二 (1+3)=4; a一个环(长度 3) b两个环(长度 1 3) 3与1 3匹配
注 对于一个a循环节 若b循环节中没有能与之匹配的 答案为零 想想为什么 不用特判累积过程中可以处理掉
AC代码
1 #include <stdio.h> 2 #include <math.h> 3 #include <string.h> 4 #include <stdlib.h> 5 #include <iostream> 6 #include <sstream> 7 #include <algorithm> 8 #include <string> 9 #include <queue> 10 #include <vector> 11 using namespace std; 12 const int maxn= 1e5+10; 13 const int mod= 1e9+7; 14 const int inf = 0x3f3f3f3f; 15 typedef long long ll; 16 int n,m; 17 int a[maxn],b[maxn]; 18 int vis[maxn],loop_a[maxn],loop_b[maxn]; //loop存循环节长度 19 int main() 20 { 21 int kase=0; 22 while(scanf("%d %d",&n,&m)!=EOF) 23 { 24 for(int i=0; i<n; i++) 25 scanf("%d",&a[i]); 26 for(int i=0; i<m; i++) 27 scanf("%d",&b[i]); 28 memset(vis, 0, sizeof(vis)); //标记是否访问过 找不相交的循环节 29 memset(loop_b, 0, sizeof(loop_b)); 30 memset(loop_a, 0, sizeof(loop_a)); 31 int cnt1=0,cnt2=0; //cnt记录循环节个数 32 for(int i = 0; i < n; i++) 33 { 34 if(!vis[i]) //过程自己模拟一下就明了了 35 { 36 int x = a[i]; 37 int len = 0; 38 while(!vis[x]) 39 { 40 len++; 41 vis[x] = 1; 42 x = a[x]; 43 } 44 if(len!=0) 45 loop_a[cnt1++]=len; //保存循环节 46 } 47 } 48 memset(vis, 0, sizeof vis); 49 for(int i = 0; i < m; i++) //b同理 50 { 51 if(!vis[i]) 52 { 53 int x = b[i]; 54 int len = 0; 55 while(!vis[x]) 56 { 57 len++; 58 vis[x] = 1; 59 x = b[x]; 60 } 61 if(len!=0) 62 loop_b[cnt2++]=len; 63 } 64 } 65 ll ans=1; 66 for(int i=0; i<cnt1; i++) //枚举a与b的循环节 试了不会超时 还有更快一些的做法 67 { 68 ll sum=0; //记录每个循环节的情况数 69 for(int j=0; j<cnt2; j++) 70 { 71 if(loop_a[i]%loop_b[j]==0) //只有是约数才能匹配 72 { 73 sum=(sum+loop_b[j])%mod; //加进去 取模 74 } 75 } 76 ans=(ans*sum)%mod; //乘到答案里面 77 } 78 printf("Case #%d: %lld\n",++kase,ans); 79 } 80 return 0; 81 }
作者水平有限 欢迎大佬纠错!
多校真jb难啊~~~~~QAQ
太菜了