The Preliminary Contest for ICPC Asia Shanghai 2019

D题

 题目大意是,有一种N个正整数组成的序列 a ,其满足

  • n2, 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;\n",i,ans);
77     }
78     __clock_t edt = clock();
79     printf("Used Time : %.3lfms\n", 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\n",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\n",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\n", 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\n",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 ,队长搜索的姿势真是优雅。

 

G题

题目大意是有一种特殊的匹配规则,原串中满足条件的子串与模式串的首字母与尾字母完全对齐(模式串长度>=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\n",mp[hashOFquery[i]]);
84         }
85     }
86     return 0;
87 }
View Code

本题关键在于想到用哈希和unordered_map和相同长度的模式串一起搜索。

 

posted on 2019-09-16 14:37  Emiya_Kiritsugu  阅读(204)  评论(0编辑  收藏  举报

导航