题目大意是,有一种N个正整数组成的序列 a ,其满足
- n≥2, and
- a1+a2+...+an=a1×a2×...×an
询问给出N(N <= 3000 )请输出有多少种序列。(序列中相等的数字等价)
这个问题很有意思,有许多相关结论
初见时会得出一个必然的形式 (1,1,1,,,2,N),而实际上还有许多其他形式,序列中的1只会贡献和不会贡献积,而积的增长又要快于和的增长。我们想要得到某个N的答案就必须得到所有序列,那些排序后等价的序列可以看成一类,统计后在计算答案时乘以一个组合数 N!/a!b!...z! 即可。
尝试枚举答案:
我们在枚举每个序列的数字时,可以通过已经枚举的数值乘积来剪枝,因为乘积是不会大于 2*N的(具体证明可见结论部分),dfs搜索过程中已经得到的mul和sum的差值就是序列中需要填1的个数,已经枚举的数字push入Stack(回溯法记录枚举),只要mul - sum 与未枚举的长度相等即可更新N的答案。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long ll; 5 const ll mod = 1e9 + 7; 6 int N; 7 ll bound; 8 ll F[3012],invF[3012]; 9 ll ans; 10 stack< int > S,S1; 11 12 ll qPow(ll a,ll n,ll m){ 13 ll ret = 1ll; 14 while(n){ 15 if(n&1) ret = ret * a % m; 16 a = a * a % m; 17 n >>= 1; 18 } 19 return ret; 20 } 21 22 void init(){ 23 F[0] = F[1] = 1ll; 24 for (int i = 2; i <= 3000; ++i) { 25 F[i] = F[i-1] * i % mod; 26 } 27 invF[3000] = qPow(F[3000],mod-2,mod); 28 for(int i = 3000;i>=1;--i){ 29 invF[i-1] = invF[i] * i %mod; 30 } 31 } 32 33 ll getans(int cntone){ 34 ll ret = F[N]; 35 S1 = S; 36 int cnt = 0, last = -1; 37 while(!S1.empty()){ 38 if(last != S1.top()){ 39 last = S1.top(); 40 ret = ret * invF[cnt] % mod; 41 cnt = 1; 42 } 43 else cnt ++; 44 S1.pop(); 45 } 46 ret = ret * invF[cnt] % mod; 47 ret = ret * invF[cntone] % mod; 48 puts(""); 49 return ret; 50 } 51 52 void dfs(int L,int R,ll mul,ll sum,ll mx){ 53 if(mul > bound) return; 54 if(mul - sum == 1ll*(R- L +1)) { 55 ans = (ans + getans(R-L+1)) % mod; 56 return ; 57 } 58 if( L > R) return; 59 for(int i=min(3000ll,mx);i>=2;--i){ 60 if(mul * i > bound) continue; 61 S.push(i); 62 dfs(L+1,R,mul*i,sum+i,min(1ll*i,mx)); 63 S.pop(); 64 } 65 } 66 67 int main(){ 68 init(); 69 70 __clock_t stt = clock(); 71 for(int i=2;i<=3000;++i){ 72 ans = 0; 73 N = i; 74 bound = 2ll*N; 75 dfs(1,N,1ll,0ll,1ll*N); 76 printf("ans[%d] = %lld; ",i,ans); 77 } 78 __clock_t edt = clock(); 79 printf("Used Time : %.3lfms ", static_cast< double >(edt - stt)/1000.0); 80 81 82 int T; 83 scanf("%d",&T); 84 while(T--) { 85 scanf("%d",&N); 86 ans = 0; 87 bound = 2ll*N; 88 dfs(1,N,1ll,0ll,3001ll); 89 printf("%lld ",ans); 90 } 91 return 0; 92 }
这样是搜索单个N的方法,似乎很快,但只能在本地打表,交表,直接提交还是会TLE(本地尝试跑3000个数字,要10~30秒)
而实际上搜索可以大幅优化。(从集训队老队长那里得到的思路)
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long ll; 5 const ll mod = 1e9 + 7; 6 int N; 7 ll bound; 8 ll F[3012],invF[3012]; 9 ll ans[3012]; 10 stack< int > S,S1; 11 12 ll qPow(ll a,ll n,ll m){ 13 ll ret = 1ll; 14 while(n){ 15 if(n&1) ret = ret * a % m; 16 a = a * a % m; 17 n >>= 1; 18 } 19 return ret; 20 } 21 22 void init(){ 23 F[0] = F[1] = 1ll; 24 for (int i = 2; i <= 3000; ++i) { 25 F[i] = F[i-1] * i % mod; 26 } 27 invF[3000] = qPow(F[3000],mod-2,mod); 28 for(int i = 3000;i>=1;--i){ 29 invF[i-1] = invF[i] * i %mod; 30 } 31 } 32 33 ll getans(ll cntnot1,ll cntone){ 34 ll ret = F[cntnot1 + cntone]; 35 S1 = S; 36 int cnt = 0, last = -1; 37 while(!S1.empty()){ 38 if(last != S1.top()){ 39 last = S1.top(); 40 ret = ret * invF[cnt] % mod; 41 cnt = 1; 42 } 43 else cnt ++; 44 S1.pop(); 45 } 46 ret = ret * invF[cnt] % mod; 47 ret = ret * invF[cntone] % mod; 48 return ret; 49 } 50 51 void dfs(int L,ll mul,ll sum,ll mx){ 52 if(mul > 6000) return; 53 ll ind = mul - sum + L; 54 if( ind<= 3000){ 55 // if(ind == 2ll) printf("%lld %lld ",mul,sum); 56 ans[ind] = (ans[ind] + getans(L,mul - sum)) % mod; 57 } 58 for(int i = min(3000ll,mx);i>=2;--i){ 59 if(mul * i > 6000ll) continue; 60 S.push(i); 61 dfs(L+1,mul*i,sum+i,min(mx,1ll*i)); 62 S.pop(); 63 } 64 } 65 66 int main(){ 67 init(); 68 // __clock_t stt = clock(); 69 dfs(0,1ll,0ll,3000ll); 70 // __clock_t edt = clock(); 71 // printf("Used Time : %.3lfms ", static_cast< double >(edt - stt)/1000.0); 72 73 int T; 74 scanf("%d",&T); 75 while(T--) { 76 scanf("%d",&N); 77 printf("%lld ",ans[N]); 78 } 79 return 0; 80 }
因为在之前的搜索过程中,当前N_i是重复搜索了其他N_j的答案的,但由于没满足N_i的序列长度,相当于不断地浪费掉很多次遇到答案的搜索过程。
所以我们可以在dfs过程不设N的序列长度,剪枝时用3000 * 2 来剪枝,在搜索点直接用 mul - sum 添加1,更新对应长度N的答案即可。(已经枚举的数字在Stack里 添加1的个数也知道,故可知对应序列长度N)
在评测机上这样只跑了 9ms ,队长搜索的姿势真是优雅。
题目大意是有一种特殊的匹配规则,原串中满足条件的子串与模式串的首字母与尾字母完全对齐(模式串长度>=2),而除了首尾外的中间部分只要求出现的各个字符次数完全相等即可。
T组样例(T<=20),原串长度N不超过1e5,有M个询问(1<=M<=2e4),保证所有询问的模式串长度之和不超过1e5。
一开始队友就给出了哈希鬼搞的想法,然而纠结的是如何在原串中搜索。
其实完全可以利用unordered_map离线存查询的模式串鬼搞哈希值,对于长度相同的模式串只在原串中用一次滑动窗口搜索鬼搞哈希值,过程中更新查询的答案即可。对于长为len的模式串,长为N的原串需要搜索 N - len + 1个窗口的哈希值,而模式串总长度不超过1e5,考虑最差的情况查询模式串长度都不相同且尽量小,len长为[2,447),共计44400765个窗口,复杂度可以接受。
鬼搞哈希:统计字符串a~z的出现次数,将数字用来哈希(数字间用'$'间隔,以防止"1234""5"与"12""345"这种碰撞),然后首尾字符也做一次哈希与之前的部分拼一起。应该有很多种哈希策略,只要抓住字符出现次数与首尾这两个特征就行。(掐头去尾统计次数时麻烦,索性一起统计,反正有首尾的哈希值兜底,不会出错)
unordered_map底层就是哈希实现,查询最快O(1)最慢O(size)。(本题里相当于将哈希再哈希,也是有意思。)只要哈希碰撞少,就能O(1)快速查改。
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef unsigned long long ull; 4 5 const int maxn = 1e5; 6 unordered_map< ull , int > mp; 7 ull base = 233; 8 int cntch[26]; 9 char strS[maxn + 10] ,strT[maxn +10]; 10 int lenS , lenT; 11 bool trylen[maxn + 10]; 12 ull hashOFquery[20000+4]; 13 14 ull myhash(char str[],int L,int R){ 15 ull ret = 0; 16 ret = ret * base + (ull)str[L]; 17 ret = ret * base + (ull)'$'; 18 for (int i = 0; i < 26; ++i) { 19 int num = cntch[i]; 20 while(num){ 21 ret = ret * base + num%10; 22 num /= 10; 23 } 24 ret = ret * base + (ull)'$'; 25 } 26 ret = ret * base + (ull)str[R]; 27 ret = ret * base + (ull)'$'; 28 return ret; 29 } 30 31 void searchS(int len){ 32 memset(cntch,0,sizeof(cntch)); 33 for (int i = 0; i < len ; i++) { 34 cntch[strS[i] - 'a'] ++; 35 } 36 ull hashcode = myhash(strS,0,len-1); 37 if(mp.count(hashcode)) 38 mp[hashcode] ++; 39 40 int L = 0 , R = len -1; 41 while(R < lenS - 1){ 42 R++; 43 cntch[strS[R]-'a'] ++; 44 cntch[strS[L]-'a'] --; 45 L++; 46 47 hashcode = myhash(strS,L,R); 48 if(mp.count(hashcode)) 49 mp[hashcode] ++; 50 } 51 } 52 53 int main(){ 54 int T; 55 scanf("%d",&T); 56 for (int cntT = 1; cntT <= T; ++cntT) { 57 58 mp.clear(); 59 memset(trylen,false,sizeof(trylen)); 60 61 int M; 62 scanf("%s",strS); 63 lenS = strlen(strS); 64 scanf("%d",&M); 65 for (int i = 0; i < M; ++i) { 66 scanf("%s",strT); 67 lenT = strlen(strT); 68 trylen[lenT] = true; 69 70 memset(cntch,0,sizeof(cntch)); 71 for (int j = 0; j < lenT; ++j) { 72 cntch[strT[j] - 'a'] ++ ; 73 } 74 hashOFquery[i] = myhash(strT,0,lenT - 1); 75 mp[hashOFquery[i]] = 0; 76 } 77 for (int i = 0; i <= maxn; ++i) { 78 if(trylen[i]) { 79 searchS(i); 80 } 81 } 82 for (int i = 0; i < M; ++i) { 83 printf("%d ",mp[hashOFquery[i]]); 84 } 85 } 86 return 0; 87 }
本题关键在于想到用哈希和unordered_map和相同长度的模式串一起搜索。