zoukankan      html  css  js  c++  java
  • 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;
    ",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 ,队长搜索的姿势真是优雅。

    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
    ",mp[hashOFquery[i]]);
    84         }
    85     }
    86     return 0;
    87 }
    View Code

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

  • 相关阅读:
    [mysql]修改 mysql 数据库端口
    [Angular]基础饼图之我如何将鼠标显示内容的数字 " 1" 去掉
    大三总结
    有符号8位整数的冒泡排序
    康托逆展开
    判断计算机是大端还是小端存储方式及分析
    C语言细节——献给入门者(三)
    C语言复杂声明
    病毒篇
    C语言细节——献给初学者(二)
  • 原文地址:https://www.cnblogs.com/Kiritsugu/p/11527132.html
Copyright © 2011-2022 走看看