寒假集训二补题与题解

A

分析

统计前缀个数,一想到字符串的前缀,我们就应该想到字典树,这个和字典一样的前缀树.
这道题目是字典树模板的略微改动,我们发现这道题目和一般字典树的查询不一样,字典树一般查询是看这个字符串是否出现,而这道题目这是统计这个字符串出现的次数.

AC代码

#include<iostream>
using namespace std;
const int N=1000010;
int n,m,idx;
int son[N][26],cnt[N];
char str[N];
// 插入一个字符串
void insert()
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int u = str[i] - 'a';
        if (!son[p][u]) son[p][u] = ++ idx;
        p = son[p][u];
    }
    cnt[p] ++ ;
}

// 查询字符串出现的次数
int query()
{
    int p = 0,res=0;
    for (int i = 0; str[i]; i ++ )
    {
        int u = str[i] - 'a';
        if (!son[p][u]) break;
        p = son[p][u];
        res+=cnt[p];
    }
    return res;
}

int main()
{
   
    cin>>n>>m;
    while(n--)
    {
        cin>>str;
        insert();
    }
    while(m--)
    {
        cin>>str;
        cout<<query()<<endl;
    }
return 0;
    
}

C

分析

对字符串S自己匹配求出next数组,然后根据定义,对于每一个i,s[i-next[i]+1~i]与s[1~next[i]]是相等的,而且不存在更大的next值满足条件.具体证明可以看李煜东金牌爷的<<算法竞赛进阶指南>>
既然如此的话,那么我们发现当i-next[i]能够整除i时候,那么s[1~i-next[i]]就是s[i-1]的最小循环元,至于次数那么也就是i/(i-next[i]).

AC代码

#include<iostream>
using namespace std;
const int N=1e6+10;
int ne[N],n,m,i,j,len,cnt;
char a[N];
void calc_ne()
{
    ne[1]=0;
    for(int i=2,j=0;i<=n;i++)
    {
        while(j>0 && a[i]!=a[j+1])
            j=ne[j];
        if (a[j+1]==a[i])
            j++;
        ne[i]=j;
    }
}
int main()
{
    while(scanf("%d",&n)!=EOF && n)
    {
        cin>>(a+1);//读到下标从一开始的位置
        printf("Test case #%d\n",++cnt);
        calc_ne();
        for(int i=2;i<=n;i++)
            if (i%(i-ne[i])==0 && i/(i-ne[i])>1)
                printf("%d %d\n",i,i/(i-ne[i]));
        puts("");
    }
return 0;
}

 

D

分析

我们发现这道题目,要求我们算出哈夫曼编码,也就是最短不重叠前缀的编码,那么我们就可以用上trie字典树的性质配合哈夫曼树进行处理.
哈夫曼树,就是满足权值*路径长度最短的树,因此我们这道题目直接可以开哈夫曼树处理即可,代码很清晰.

AC代码

#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
#define pll pair<long long,long long>
const int N=101000;
long long n,m,i,j,k,ans,x;
priority_queue<pll,vector<pll>,greater<pll> > p;
int main()
{
    cin.tie(0);
    cin>>n>>k;
    for(i=1;i<=n;i++)
    {
        cin>>x;
        p.push(make_pair(x,0));
    }
    while((p.size()-1)%(k-1)!=0) 
        p.push(pll(0,0));
    while(p.size()>=k)
    {
        long long deep=-1,temp=0;
        for(j=1;j<=k;j++)
        {
            pll dx=p.top();
            p.pop();
            temp+=dx.first;
            deep=max(deep,dx.second);
        }
        p.push(make_pair(temp,deep+1));
        ans+=temp;
    }
    cout<<ans<<endl<<p.top().second;
    return 0;
}

 

E

分析

经典哈夫曼树的模型,每次合并重量最小的两堆果子即可。使用小根堆维护所有果子,每次弹出堆顶的两堆果子,并将其合并,合并之后将两堆重量之和再次插入小根堆中。

AC代码

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
int main()
{
    int n;  
    cin>>n;
    priority_queue<int ,vector<int>,greater<int>>heap;//小根堆
    while(n--)
    {
        int x;
        cin>>x;
        heap.push(x);
    }
    int res=0;
    while(heap.size()>1)
    {
        int a = heap.top(); heap.pop();
        int b = heap.top(); heap.pop();
        res += a + b;
        heap.push(a + b);
        
    }
    cout<<res<<endl;
    
return 0;
}

 

F

分析

首先我们观察这道题目其实和之前的E题Largest Rectangle in a Histogram,十分相似,但是之前这道题的水平面是已经确定好了,

那么这道题目是不是也可以这么做呢?我们可以枚举水平面,看每一个平面上的最大值即可.

AC代码

#include<iostream>
using namespace std;
const int N=1010;
int n,m;
int s[N][N],l[N],r[N];   
int q[N];                  
int work(int h[])          
{
    h[0]=h[m+1]=-1;     
    int tt=0;             
    q[0]=0;
    for (int i=1;i<=m;i++)
    {
        while(h[q[tt]]>=h[i]) tt--;
        l[i]=q[tt];    
        q[++tt]=i;  
    }
    tt=0;
    q[0]=m+1;
    for(int i=m;i;i--)
    {
        while(h[q[tt]]>=h[i]) tt--;
        r[i]=q[tt];
        q[++tt]=i;
    }

    int res=0;
    for(int i=1;i<=m;i++)
        res=max(res,h[i]*(r[i]-l[i]-1));

    return res;
}

int main()
{
    cin>>n>>m;

    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            char c;
            cin>>c;
            if(c=='F') s[i][j]=s[i-1][j]+1;        
        }

    int res=0;
    for(int i=1;i<=n;i++) res=max(res,work(s[i]));

    cout<<res*3<<endl;

    return 0;
}

 

G

分析

Trie数组相关应用:
1.每次先把号码一个一个存进str数组,每个数字位都++
2.判断如果一个号码每一位出现次数均大于1,说明其为前缀,输出”NO”

AC代码

#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main()
{
    string s[100005];
    char p[12];
    int t,flag=0,n,i,j;
    cin>>t;
    while(t--)
    {
        flag=0;
        cin>>n;
        for(int i=0;i<n;i++)
        {
           cin>>p;
           s[i]=p;
        }
        sort(s,s+n);
        for(int i=1;i<n;i++)
        {
            if(s[i].size()>=s[i-1].size())
            {
                for(j=0;j<s[i-1].size();j++)
                   if(s[i][j]!=s[i-1][j])   break;
                      
                 if(j>=s[i-1].size())
                      flag=1; 
            }
        }
        if(flag) cout<<"NO"<<endl;
        else cout<<"YES"<<endl;
    }
    return 0;

}

 H

分析

向这样的动态求第k小的值,并且k的变化要么+1 要么-1 要么不变的话,就可以用对顶堆数据结构。

当加入一个数x的话判断如果大根堆为空或者x >= 小根堆堆顶的话直接加入小根堆中
反之加入到大根堆中,并且把大根堆堆顶的数添加到小根堆中,并把大根堆堆顶删除
当来到一次get操作

AC代码

#include<iostream>
#include<queue>
using namespace std;
const int N=3e4+10;
priority_queue<int> q;
priority_queue<int, vector<int> ,greater<int> > q2;
int num[N],n,m,x;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>num[i];
    int k=1;
    for(int i=1;i<=m;i++)
    {
        cin>>x;
        while(k<=x)
        {
            q2.push(num[k]);
            if(!q.empty()&&q.top()>q2.top())
            {
                int t=q.top();
                q.pop();
                q2.push(t);
                t=q2.top();
                q2.pop();
                q.push(t);
            }
            k++;
        }
        cout<<q2.top()<<endl;
        int t=q2.top();
        q2.pop();
        q.push(t);
    }
    return 0;
}

 

上一篇:Java控制流程学习笔记


下一篇:【SSL1271】排序I【堆】