第八次练习赛标程

  数据结构课程最后一部分的内容其实很难在上机中得到良好的考查,我们竭尽所能地出了这些题目,私以为是这几年数据结构课程中,对最后一部分知识点覆盖最广的一次练习赛……当然,也因为这部分知识确实有一定的难度,题目也显得不是很好做;同时因为较大的数据量,也对5分钟一重启的评测机提出了严峻的挑战……

  因为这次劳模渣诚写解题报告(好吧D题和G题是我写的),为了不浪费其余的标程,就扔这里好了。

  A. 内排序(一)

  陈题,排个序罢了没啥说的,是个O(nlogn)的都能过。

#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=10005;
int a[MAXN];
void QuickSort(int l,int r)
{
    if(l>=r)
        return;
    int p=l,q=r,x=a[l];
    while(p<q)
    {
        while(p<q&&a[q]>=x)
            --q;
        if(p<q)
            a[p++]=a[q];
        while(p<q&&a[p]<x)
            ++p;
        if(p<q)
            a[q--]=a[p];
    }
    a[p]=x;
    QuickSort(l,p-1);
    QuickSort(p+1,r);
}
char buf[MAXN*100];
int main()
{
    int t;
    scanf("%d",&t);
    getchar();
    while(t--)
    {
        gets(buf);
        int l=strlen(buf),n=0,k=0;
        while(k<l)
        {
            a[n]=0;
            while(buf[k]!=' '&&k<l)
                a[n]=a[n]*10+(buf[k++]-'0');
            ++n;
            ++k;
        }
        QuickSort(0,n-1);
        for(int i=0; i<n; ++i)
            printf("%d ",a[i]);
        putchar('\n');
    }
}

  B. 水水的二分……?

  渣诚出的不错的二分题,不过被具有良好姿势的我随随便便艹过了……

#include<cstdio>
using namespace std;
const int MAXN=250005;
int a[MAXN],n;
int lower_bound(int x)
{
    int l=0,r=n;
    while(l<r)
    {
        int m=l+r>>1;
        x>a[m]?l=m+1:r=m;
    }
    return l;
}
int main()
{
    int m,x;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=0; i<n; ++i)
            scanf("%d",&a[i]);
        while(m--)
        {
            scanf("%d",&x);
            int k=lower_bound(x);
            k==n||a[k]!=x?puts("error"):printf("%d\n",k+1);
        }
    }
}
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=250005;
int a[MAXN];
int main()
{
    int n,m,x;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=0; i<n; ++i)
            scanf("%d",&a[i]);
        while(m--)
        {
            scanf("%d",&x);
            int k=lower_bound(a,a+n,x)-a;
            k==n||a[k]!=x?puts("error"):printf("%d\n",k+1);
        }
    }
}

  C. 叫我第k名!

  寻找序列第K大,三个分值依次要求O(n^2)、O(nlogn)、O(n)的做法。满分的做法需要借用快排的思想。

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=1000005;
int a[MAXN];
int kth_element(int l,int r,int k)
{
    int x=a[r],p=l,q=r-1;
    while(true)
    {
        while(a[p]<x)
            ++p;
        while(a[q]>=x)
            --q;
        if(p>=q)
            break;
        swap(a[p],a[q]);
    }
    swap(a[p],a[r]);
    int tmp=p-l+1;
    if(tmp==k)
        return a[p];
    else if(tmp>k)
        return kth_element(l,p-1,k);
    else
        return kth_element(p+1,r,k-tmp);
}
int main()
{
    int n,k;
    while(~scanf("%d%d",&n,&k))
    {
        for(int i=0; i<n; ++i)
            scanf("%d",&a[i]);
        printf("%d\n",kth_element(0,n-1,n-k+1));
    }
}

  当然啦,STL是个很神奇的东西……

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=1000005;
int a[MAXN];
int main()
{
    int n,k;
    while(~scanf("%d%d",&n,&k))
    {
        for(int i=0; i<n; ++i)
            scanf("%d",&a[i]);
        nth_element(a,a+n-k,a+n);
        printf("%d\n",a[n-k]);
    }
}

  这道题是相对比较简单的全局第K大,到了ACM级别还要会处理无修改区间第K大,可以用可持久化权值线段树做,也可以用划分树做(划分树的建树原理就是借用快排的处理过程);甚至有修改区间第K大,需要树状数组套可持久化权值线段树,不再多谈。

  D. 更多的Papers!

  这道题的解题报告是我写的,裸的堆维护:

  “这道题建立Huffman树会超时,因为书上Huffman树的建树过程是O(n^2)的;但并不代表我们不可以利用Huffman树的思想。我们知道Huffman树是基于贪心思想的,也就是每次取出权值最小的两个点,合并成一个点,合并后的权值为两点之和,循环处理直到只剩下一个点。于是我们面对的问题是如何在O(logn)的时间内取出一个动态的集合的最小值。这样,我们会自然而然地想到堆(如果你想到了优先队列也不错,优先队列的实现一般也基于堆)。堆的性质在堆排序中有介绍,不再多谈,这里我们需要的是小根堆。我们要实现的操作有两种,一是取出堆的最小值,其实就是取出根的值,然后维护一下堆的性质(将权值较小的子节点调整到根上,递归地调整下去);二是将一个值放入堆中,方法是将值放在数组的最后(也就是某个叶子节点的子节点上),然后进行一次维护(将其不断与父节点比较,如果权值小于父节点则与父节点交换,一路递归上去)。”

  然后我有自己的堆模板,也搜集了几个其它人的堆模板,以及STL优先队列,扔上来。

#include<cstdio>
#include<climits>
#include<algorithm>
using namespace std;
const int MAXN=100005;
struct BinaryHeap
{
    int heap[MAXN<<1],id[MAXN<<1],pos[MAXN<<1],n,cnt;
    BinaryHeap():n(0),cnt(0) {};
    BinaryHeap(int array[],int offset):n(0),cnt(0)
    {
        for(int i=0; i<offset; ++i)
        {
            heap[++n]=array[i];
            id[n]=pos[n]=n;
        }
        for(int i=n>>1; i>=1; --i)
            down(i);
    }
    void init()
    {
        n=cnt=0;
    }
    inline void up(int i)
    {
        int x=heap[i],y=id[i];
        for(int j=i>>1; j>=1; j>>=1)
        {
            if(heap[j]<=x)
                break;
            heap[i]=heap[j];
            id[i]=id[j];
            pos[id[i]]=i;
            i=j;
        }
        heap[i]=x;
        id[i]=y;
        pos[y]=i;
    }
    inline void down(int i)
    {
        int x=heap[i],y=id[i];
        for(int j=i<<1; j<=n; j<<=1)
        {
            j+=j<n&&heap[j]>heap[j+1];
            if(heap[j]>=x)
                break;
            heap[i]=heap[j];
            id[i]=id[j];
            pos[id[i]]=i;
            i=j;
        }
        heap[i]=x;
        id[i]=y;
        pos[y]=i;
    }
    void push(int val)
    {
        heap[++n]=val;
        id[n]=++cnt;
        pos[id[n]]=n;
        up(n);
    }
    int pop()
    {
        swap(heap[1],heap[n]);
        swap(id[1],id[n--]);
        pos[id[1]]=1;
        down(1);
        return id[n+1];
    }
    void change(int i,int val)
    {
        heap[pos[i]]=val;
        down(pos[i]);
        up(pos[i]);
    }
    void erase(int i)
    {
        heap[pos[i]]=INT_MIN;
        up(pos[i]);
        pop();
    }
    int get(int i)
    {
        return heap[pos[i]];
    }
    int top()
    {
        return heap[1];
    }
    int size()
    {
        return n;
    }
    bool empty()
    {
        return n==0;
    }
} heap;
int main()
{
    int n,x;
    while(~scanf("%d",&n))
    {
        heap.init();
        for(int i=0; i<n; ++i)
        {
            scanf("%d",&x);
            heap.push(x);
        }
        int ans=0;
        while(--n)
        {
            int x=heap.top();
            heap.pop();
            int y=heap.top();
            heap.pop();
            ans+=x+y;
            heap.push(x+y);
        }
        printf("%d\n",ans);
    }
}
#include<cstdio>
using namespace std;
const int MAXN=100005;
int heap[MAXN],size;
void push(int x)
{
    int k=size++;
    while(k>0)
    {
        int p=(k-1)>>1;
        if(heap[p]<=x)
            break;
        heap[k]=heap[p];
        k=p;
    }
    heap[k]=x;
}
int pop()
{
    int ret=heap[0],x=heap[--size],k=0;
    while((k<<1)+1<size)
    {
        int a=(k<<1)+1,b=a+1;
        if(b<size&&heap[b]<heap[a])
            a=b;
        if(heap[a]>=x)
            break;
        heap[k]=heap[a];
        k=a;
    }
    heap[k]=x;
    return ret;
}
int main()
{
    int n,x;
    while(~scanf("%d",&n))
    {
        size=0;
        while(n--)
        {
            scanf("%d",&x);
            push(x);
        }
        int ans=0;
        while(size>1)
        {
            int x=pop();
            int y=pop();
            push(x+y);
            ans+=x+y;
        }
        printf("%d\n",ans);
    }
}
#include<cstdio>
using namespace std;
const int MAXN=100005;
struct Heap
{
    int h[MAXN],n;
    void init()
    {
        n=0;
    }
    void push(int x)
    {
        int p;
        for(p=++n; p>1&&x<h[p>>1]; p>>=1)
            h[p]=h[p>>1];
        h[p]=x;
    }
    bool pop(int &x)
    {
        if(!n)
            return false;
        int p=1;
        x=h[p];
        for(int c=2; c<n&&h[c+=(c<n-1&&h[c+1]<h[c])]<h[n]; c<<=1)
        {
            h[p]=h[c];
            p=c;
        }
        h[p]=h[n--];
        return true;
    }
} heap;
int main()
{
    int n,x;
    while(~scanf("%d",&n))
    {
        heap.init();
        for(int i=0; i<n; ++i)
        {
            scanf("%d",&x);
            heap.push(x);
        }
        int ans=0,x,y;
        while(--n)
        {
            heap.pop(x);
            heap.pop(y);
            ans+=x+y;
            heap.push(x+y);
        }
        printf("%d\n",ans);
    }
}
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
int main()
{
    int n,x;
    while(~scanf("%d",&n))
    {
        priority_queue<int,vector<int>,greater<int> > pq;
        while(n--)
        {
            scanf("%d",&x);
            pq.push(x);
        }
        int ans=0;
        while(pq.size()>1)
        {
            int x=pq.top();
            pq.pop();
            int y=pq.top();
            pq.pop();
            ans+=x+y;
            pq.push(x+y);
        }
        printf("%d\n",ans);
    }
}

  E. "逆序对数"来袭

  陈题,这个题实际上是渣诚找错题了……本来只打算出一道求逆序数的,结果一不留神进阶了一下。逆序数通常用归并排序来做。

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=50005;
int data[MAXN],a[MAXN],tmp[MAXN],num;
void MergeSort(int l,int r)
{
    int m=l+r>>1;
    if(l<m)
        MergeSort(l,m);
    if(m+1<r)
        MergeSort(m+1,r);
    int p=l,q=m+1,k=0;
    while(p<=m&&q<=r)
    {
        if(a[p]<=a[q])
            tmp[k++]=a[p++];
        else
        {
            tmp[k++]=a[q++];
            num+=m-p+1;
        }
    }
    while(p<=m)
        tmp[k++]=a[p++];
    while(q<=r)
        tmp[k++]=a[q++];
    for(int i=l; i<=r; ++i)
        a[i]=tmp[i-l];
}
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=0; i<n; ++i)
        {
            scanf("%d",&data[i]);
            a[i]=data[i];
        }
        num=0;
        MergeSort(0,n-1);
        int ans=num;
        for(int i=0; i<n; ++i)
        {
            num+=n-2*data[i]-1;
            ans=min(ans,num);
        }
        printf("%d\n",ans);
    }
}

  也可以用树状数组或者线段树做,这是课程外的东西了;复杂度与归并一致。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=50005;
struct data
{
    int val,idx;
    bool operator<(const data &oth) const
    {
        if(val==oth.val)
            return idx<oth.idx;
        return val<oth.val;
    }
} a[MAXN];
inline int lowbit(int x)
{
    return x&-x;
}
int bit[MAXN],n,b[MAXN];
void add(int x,int val)
{
    for(int i=x; i<=n; i+=lowbit(i))
        bit[i]+=val;
}
int sum(int x)
{
    int sum=0;
    for(int i=x; i>0; i-=lowbit(i))
        sum+=bit[i];
    return sum;
}
int inv_num()
{
    for(int i=1; i<=n; ++i)
        b[a[i].idx]=i;
    memset(bit,0,sizeof(bit));
    int ret=0;
    for(int i=1; i<=n; ++i)
    {
        add(b[i],1);
        ret+=i-sum(b[i]);
    }
    return ret;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1; i<=n; ++i)
        {
            scanf("%d",&a[i].val);
            a[i].idx=i;
        }
        sort(a+1,a+n+1);
        int num=inv_num();
        int ans=num;
        for(int i=1; i<=n; ++i)
        {
            num+=n-2*b[i]+1;
            ans=min(ans,num);
        }
        printf("%d\n",ans);
    }
}
#include<cstdio>
#include<algorithm>
using namespace std;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
const int MAXN=50005;
struct data
{
    int val,idx;
    bool operator<(const data &oth) const
    {
        if(val==oth.val)
            return idx<oth.idx;
        return val<oth.val;
    }
} a[MAXN];
int sum[MAXN<<2],b[MAXN],n;
void PushUp(int rt)
{
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt)
{
    if(l==r)
    {
        sum[rt]=0;
        return;
    }
    int m=l+r>>1;
    build(lson);
    build(rson);
    PushUp(rt);
}
void update(int p,int val,int l,int r,int rt)
{
    if(l==r)
    {
        sum[rt]+=val;
        return;
    }
    int m=l+r>>1;
    if(p<=m)
        update(p,val,lson);
    else
        update(p,val,rson);
    PushUp(rt);
}
int query(int L,int R,int l,int r,int rt)
{
    if(L<=l&&r<=R)
        return sum[rt];
    int m=l+r>>1,ret=0;
    if(L<=m)
        ret+=query(L,R,lson);
    if(m<R)
        ret+=query(L,R,rson);
    return ret;
}
int inv_num()
{
    for(int i=1; i<=n; ++i)
        b[a[i].idx]=i;
    build(1,n,1);
    int ret=0;
    for(int i=1; i<=n; ++i)
    {
        update(b[i],1,1,n,1);
        ret+=i-query(1,b[i],1,n,1);
    }
    return ret;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1; i<=n; ++i)
        {
            scanf("%d",&a[i].val);
            a[i].idx=i;
        }
        sort(a+1,a+n+1);
        int num=inv_num();
        int ans=num;
        for(int i=1; i<=n; ++i)
        {
            num+=n-2*b[i]+1;
            ans=min(ans,num);
        }
        printf("%d\n",ans);
    }
}

  F. 我有特殊的排序技巧

  基数排序,这里说点题外的。基数排序是桶排序的变形,十进制相当于是十个桶,但是对于排序来说用什么进制表示是无所谓的,所以我们不必拘泥于十个桶,我拿128个桶测试了一下(下面代码中将BASE常量从10改为128),速度还会更快一些。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define BASE 10
const int MAXN=10000005;
int a[MAXN],b[MAXN],bucket[BASE],n;
void RadixSort()
{
    int radix=1,amax=0;
    for(int i=0; i<n; ++i)
        amax=max(amax,a[i]);
    while(amax>=radix)
    {
        memset(bucket,0,sizeof(bucket));
        for(int i=0; i<n; ++i)
            ++bucket[(a[i]/radix)%BASE];
        for(int i=1; i<BASE; ++i)
            bucket[i]+=bucket[i-1];
        for(int i=n-1; i>=0; --i)
            b[--bucket[(a[i]/radix)%BASE]]=a[i];
        for(int i=0; i<n; ++i)
            a[i]=b[i];
        radix*=BASE;
    }
}
int main()
{
    while(~scanf("%d",&n))
    {
        for(int i=0; i<n; ++i)
            scanf("%d",&a[i]);
        RadixSort();
        for(int i=0; i<n; ++i)
            printf("%d ",a[i]);
        putchar('\n');
    }
}

  此外还有另一种不基于比较的排序叫做计数排序,作为拓展。其实基数排序的过程中存在着计数排序;在求后缀数组等结构和算法中会用到它。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=10000005;
int a[MAXN],cnt[105],rank[MAXN],n;
void CountingSort()
{
    int amax=0;
    memset(cnt,0,sizeof(cnt));
    for(int i=0; i<n; ++i)
    {
        ++cnt[a[i]];
        amax=max(amax,a[i]);
    }
    for(int i=1; i<=amax; ++i)
        cnt[i]+=cnt[i-1];
    for(int i=n-1; i>=0; --i)
        rank[--cnt[a[i]]]=i;
}
int main()
{
    while(~scanf("%d",&n))
    {
        for(int i=0; i<n; ++i)
            scanf("%d",&a[i]);
        CountingSort();
        for(int i=0; i<n; ++i)
            printf("%d ",a[rank[i]]);
        putchar('\n');
    }
}

  当然了,很多人也知道这样乱搞才是王道……又快又省内存。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=10000005;
int cnt[105];
int main()
{
    int n,x;
    while(~scanf("%d",&n))
    {
        memset(cnt,0,sizeof(cnt));
        int amax=0;
        for(int i=0; i<n; ++i)
        {
            scanf("%d",&x);
            ++cnt[x];
            amax=max(amax,x);
        }
        for(int i=1; i<=amax; ++i)
            for(int j=0; j<cnt[i]; ++j)
                printf("%d ",i);
        putchar('\n');
    }
}

  G. 字符串hash

  我写了这道题的解题报告:

  “Hash是一个拥有广泛应用的思想,良好构造的Hash表可以做到近乎O(1)的查询效率。构造Hash表不可避免的问题是处理Hash冲突。一方面,我们要选择良好的Hash函数使得冲突的几率尽可能减小;此外良好的Hash函数可以使得到的Hash地址较均匀地分布在内存单元上,减轻堆积问题,提高查询效率。题目已经给出了很多的Hash函数,这些函数各有优劣,但都会遇到冲突的问题。所以关键在于如何处理调整Hash函数也无法解决的冲突。有的童鞋使用了开放定址法中的线性探查法,但遇到了严重的堆积问题引起TLE;有的人使用了两个甚至多个Hash函数进行多重校验,但依然无法解决所有的冲突;所以这道题正确合理的姿势是使用拉链法,因为它可以保证不引起冲突。拉链法并不是什么神秘的东西,事实上我们可以很快地联想到邻接表,它们具有完全一致的结构;这样我们来计算一下,10^6个长度不超过10的字符串,相当于邻接表中有10^6个点,空间上不会遇到很大的挑战。接下来我们要做的就是选择一个良好的Hash函数,使得Hash地址尽可能均摊,也就是使得每一条链的长度保持均匀,这样就逼近了常数级的查询效率。”

  最后是我的标程,分别是用list写的,用前向星写的,以及用来出数据实际会TLE的unordered_map。

#include<cstdio>
#include<cstring>
#include<string>
#include<list>
using namespace std;
const int MOD=1000003;
unsigned int BKDRHash(char *str)
{
    unsigned int seed=131;
    unsigned int hash=0;
    while(*str)
        hash=hash*seed+(*str++);
    return hash%MOD;
}
list<string> ht[MOD];
list<string>::iterator it;
int main()
{
    int n,m;
    char str[12];
    scanf("%d",&n);
    while(n--)
    {
        scanf("%s",str);
        ht[BKDRHash(str)].push_back(str);
    }
    scanf("%d",&m);
    while(m--)
    {
        scanf("%s",str);
        int h=BKDRHash(str);
        bool flag=false;
        for(it=ht[h].begin(); !flag&&it!=ht[h].end(); ++it)
            if(strcmp(it->c_str(),str)==0)
                flag=true;
        puts(flag?"Yes":"No");
    }
}
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=1000005;
const int MOD=1000003;
struct HashTable
{
    int head[MOD];
    int next[MAXN];
    char val[MAXN][12];
    int tot;
    void init()
    {
        tot=0;
        memset(head,0xff,sizeof(head));
    }
    void add(int h,char *v)
    {
        strcpy(val[tot],v);
        next[tot]=head[h];
        head[h]=tot++;
    }
} ht;
unsigned int BKDRHash(char *str)
{
    unsigned int seed=131;
    unsigned int hash=0;
    while(*str)
        hash=hash*seed+(*str++);
    return hash%MOD;
}
int main()
{
    int n,m;
    char str[12];
    scanf("%d",&n);
    ht.init();
    while(n--)
    {
        scanf("%s",str);
        ht.add(BKDRHash(str),str);
    }
    scanf("%d",&m);
    while(m--)
    {
        scanf("%s",str);
        bool flag=false;
        for(int i=ht.head[BKDRHash(str)]; !flag&&~i; i=ht.next[i])
            if(strcmp(ht.val[i],str)==0)
                flag=true;
        puts(flag?"Yes":"No");
    }
}
#include<cstdio>
#include<string>
#ifdef __GXX_EXPERIMENTAL_CXX0X__
#include<unordered_map>
#else
#include<tr1/unordered_map>
#endif
using namespace std;
using namespace std::tr1;
unordered_map<string,bool> data;
int main()
{
    int n,m;
    char str[12];
    scanf("%d",&n);
    while(n--)
    {
        scanf("%s",str);
        data[str]=true;
    }
    scanf("%d",&m);
    while(m--)
    {
        scanf("%s",str);
        puts(data[str]?"Yes":"No");
    }
}

  所以我们分别用变种的方式,考查到了对快排、归并排序的理解,对堆排序中堆的理解,对基数排序的应用,堪称有史以来最全的一次对排序的考查……此外还考查了二分和Hash,尤其是Hash是前几届一直缺失的题型。说这么多就是想表达一下心中满溢的自豪之情Orz……那么最后一次练习赛也就结束了,数据结构上机相关的一切事情也就结束了;但是对童鞋们来说与数据结构相伴的日子才刚刚开始,只要你决心将码农之路走下去。

  愿一路走好。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值