第七次报告和标准的练习过程中解决

  比较大的坡度难度的课题,注册很水的问题,最难的问题是非常困难的;童鞋们也能看到一些问题进行了直接从主要采取OJ的ACM称号,所以,总体来说难度可以说是大多数机器的问题和以前的练习。习的过题数计入平时成绩,能够看作一次超长时长的上机,终于board的情况能够说在意料之中,但也稍微超出了一点预估;希望成绩中没有非常大的水分。

  A. 邀请函

  难度中等偏下。

求有向图中一个点出发到全部点再返回的最短路之和。

我们注意到从一个点到全部点就是纯粹的单源最短路;而从全部点返回一个点。倘若我们把图中全部的边反向,就又变成了单源最短路。所以这道题的做法就是对原图和反图各求一遍单源最短路。这里请童鞋们自行区分一下反图和补图的概念。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1005;
const int INF=0x3f3f3f3f;
int g[MAXN][MAXN],dist[MAXN],n;
bool vis[MAXN];
void dijkstra(int src)
{
    memset(dist,0x3f,sizeof(dist));
    memset(vis,false,sizeof(vis));
    dist[src]=0;
    for(int i=1; i<=n; ++i)
    {
        pair<int,int> tmp=make_pair(INF,-1);
        for(int j=1; j<=n; ++j)
            if(!vis[j]&&dist[j]<tmp.first)
                tmp=make_pair(dist[j],j);
        if(!~tmp.second)
            break;
        vis[tmp.second]=true;
        for(int j=1; j<=n; ++j)
            dist[j]=min(dist[j],tmp.first+g[tmp.second][j]);
    }
}
int main()
{
    int m,u,v,l;
    while(~scanf("%d%d",&n,&m))
    {
        memset(g,0x3f,sizeof(g));
        for(int i=1; i<=n; ++i)
            g[i][i]=0;
        while(m--)
        {
            scanf("%d%d%d",&u,&v,&l);
            g[u][v]=min(g[u][v],l);
        }
        int ans=0;
        dijkstra(1);
        for(int i=1; i<=n; ++i)
            ans+=dist[i];
        for(int i=1; i<=n; ++i)
            for(int j=i+1; j<=n; ++j)
                swap(g[i][j],g[j][i]);
        dijkstra(1);
        for(int i=1; i<=n; ++i)
            ans+=dist[i];
        printf("%d\n",ans);
    }
}
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int MAXN=1005;
const int MAXM=100005;
struct graph
{
    int head[MAXN];
    int to[MAXM];
    int next[MAXM];
    int len[MAXM];
    int tot;
    void init()
    {
        tot=0;
        memset(head,0xff,sizeof(head));
    }
    void add(int u,int v,int w)
    {
        to[tot]=v;
        len[tot]=w;
        next[tot]=head[u];
        head[u]=tot++;
    }
} g,rg;
int dist[MAXN];
bool inque[MAXN];
void spfa(graph &G,int src)
{
    memset(dist,0x3f,sizeof(dist));
    memset(inque,false,sizeof(inque));
    dist[src]=0;
    queue<int> q;
    q.push(src);
    inque[src]=true;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        inque[x]=false;
        for(int i=G.head[x]; ~i; i=G.next[i])
        {
            int y=G.to[i];
            if(dist[x]+G.len[i]<dist[y])
            {
                dist[y]=dist[x]+G.len[i];
                if(!inque[y])
                {
                    q.push(y);
                    inque[y]=true;
                }
            }
        }
    }
}
int main()
{
    int n,m,u,v,l;
    while(~scanf("%d%d",&n,&m))
    {
        g.init();
        rg.init();
        while(m--)
        {
            scanf("%d%d%d",&u,&v,&l);
            g.add(u,v,l);
            rg.add(v,u,l);
        }
        int ans=0;
        spfa(g,1);
        for(int i=1; i<=n; ++i)
            ans+=dist[i];
        spfa(rg,1);
        for(int i=1; i<=n; ++i)
            ans+=dist[i];
        printf("%d\n",ans);
    }
}

  B. 寻找下界

  这道题我预谋已久了……题目原打算直接起名叫lower_bound,后来不希望大家使用STL,故而改成不伦不类的中文名;最后干脆直接禁掉了可能利用lower_bound()函数的全部头文件。原因非常easy。二分尽管是每个程序员必须掌握的技巧,但又快又准地写好二分相同是一个学问,找一个序列中的某个数仅仅只是是最基础的操作罢了。

这道题,找的就是大于等于某个数的第一个数,同理,希望大家能自行练习寻找一个序列中大于某个数的第一个数(即upper_bound()函数)。话不多说,扔上三种姿势。

#include<cstdio>
using namespace std;
const int MAXN=100005;
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,k; while(~scanf("%d%d",&n,&m)) { for(int i=0; i<n; ++i) scanf("%d",&a[i]); while(m--) { scanf("%d",&k); int idx=lower_bound(k); printf("%d\n",idx==n?-1:a[idx]); } } }

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

  C. Cache

  由于期末上机考试的临近,我们出了这道题,算是对链表操作的回想。这道题其意义在于考查了链表链节的合并和分离操作,倘若能够熟练处理这两个操作,事实上就具备了写块状链表的一切知识和技巧。块状链表不在课程要求内。但有心搞ACM的童鞋最好还是深入探究一下,自行查找资料并手动实现出块状链表的结构和功能。

  由于有些童鞋说这道题用STL写各种RE,于是我就写了一个能够AC的STL版本号供參考。

#include<iostream>
#include<string>
#include<list>
using namespace std;
list<string> data;
list<string>::iterator cur,tmp;
int main()
{
    int n,m,k;
    while(cin>>n>>m)
    {
        string str,op;
        while(n--)
        {
            cin>>str;
            data.push_back(str);
        }
        cur=data.begin();
        while(m--)
        {
            cin>>op;
            switch(op[4])
            {
            case 'R':
                ++cur;
                if(cur==data.end())
                    --cur;
                break;
            case 'L':
                if(cur!=data.begin())
                    --cur;
                break;
            case 'I':
                tmp=cur;
                ++tmp;
                if(tmp!=data.end())
                {
                    (*cur)+=(*tmp);
                    data.erase(tmp);
                }
                break;
            case 'D':
                cin>>k;
                str=(*cur).substr(k,(*cur).size()-k);
                (*cur)=(*cur).substr(0,k);
                tmp=cur;
                ++tmp;
                data.insert(tmp,str);
                break;
            }
        }
        for(tmp=data.begin(); tmp!=data.end(); ++tmp)
            cout<<(*tmp)<<' ';
        cout<<'\n'<<(*cur)<<'\n';
        data.clear();
    }
}

  D. 行者无疆

  从过题数能够看出。这是毫无疑问的最难题。大概一个多月前的一次训练赛,我和Thor合力搞了将近一个小时才做出来。只是当时那道题时限更严苛。后来我们总结了最短路的一些时间复杂度上的经验并写在了这篇blog里。我的做法是如果起点和终点也有充电桩,然后以每一个充电桩为起点跑一遍单源最短路,接着把全部的充电桩放在一起又一次建图,倘若之间的最短路径小于要求则有一条边。然后再从起点跑一遍单源最短路就可以。当然。这道题也能够直接改动dijkstra,理论上速度会更快,但显然从思考难度上又高了一些,这里不再讨论。

  由于有两次建图且分别求了单源最短路。第一次是稀疏图,第二次是稠密图,所以我多写了一种用spfa处理第一次建图的写法,由于稀疏图下spfa相对于dijkstra的优势还是比較明显的,而稠密图下又会慢于dijkstra。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=305;
const int INF=0x3f3f3f3f;
int city[MAXN][MAXN],pile[MAXN][MAXN],s[MAXN],dist[MAXN];
bool vis[MAXN];
void dijkstra(int g[][MAXN],int n,int src)
{
    memset(dist,0x3f,sizeof(dist));
    memset(vis,false,sizeof(vis));
    dist[src]=0;
    for(int i=1; i<=n; ++i)
    {
        pair<int,int> tmp=make_pair(INF,-1);
        for(int j=1; j<=n; ++j)
            if(!vis[j]&&dist[j]<tmp.first)
                tmp=make_pair(dist[j],j);
        if(!~tmp.second)
            break;
        vis[tmp.second]=true;
        for(int j=1; j<=n; ++j)
            dist[j]=min(dist[j],tmp.first+g[tmp.second][j]);
    }
}
int main()
{
    int n,m,v,k,x,y,d;
    while(~scanf("%d%d%d",&n,&m,&v))
    {
        memset(city,0x3f,sizeof(city));
        for(int i=1; i<=n; ++i)
            city[i][i]=0;
        memset(pile,0x3f,sizeof(pile));
        while(m--)
        {
            scanf("%d%d%d",&x,&y,&d);
            if(d<city[x][y])
                city[x][y]=city[y][x]=d;
        }
        scanf("%d",&k);
        for(int i=1; i<=k; ++i)
            scanf("%d",&s[i]);
        s[++k]=1;
        s[++k]=n;
        for(int i=1; i<=k; ++i)
            pile[i][i]=0;
        for(int i=1; i<=k; ++i)
        {
            dijkstra(city,n,s[i]);
            for(int j=i+1; j<=k; ++j)
            {
                int d=dist[s[j]];
                if(d>v*5)
                    d=INF;
                pile[i][j]=pile[j][i]=d;
            }
        }
        dijkstra(pile,k,k-1);
        printf("%d\n",dist[k]==INF?

-1:dist[k]); } }

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int MAXN=305;
const int MAXM=20005;
const int MAXK=55;
const int INF=0x3f3f3f3f;
struct graph
{
    int head[MAXN];
    int to[MAXM];
    int next[MAXM];
    int len[MAXM];
    int tot;
    void init()
    {
        tot=0;
        memset(head,0xff,sizeof(head));
    }
    void add(int u,int v,int w)
    {
        to[tot]=v;
        len[tot]=w;
        next[tot]=head[u];
        head[u]=tot++;
    }
} city;
int dist[MAXN];
bool inque[MAXN];
void spfa(int src)
{
    memset(dist,0x3f,sizeof(dist));
    memset(inque,false,sizeof(inque));
    dist[src]=0;
    queue<int> q;
    q.push(src);
    inque[src]=true;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        inque[x]=false;
        for(int i=city.head[x]; ~i; i=city.next[i])
        {
            int y=city.to[i];
            if(dist[x]+city.len[i]<dist[y])
            {
                dist[y]=dist[x]+city.len[i];
                if(!inque[y])
                {
                    q.push(y);
                    inque[y]=true;
                }
            }
        }
    }
}
int pile[MAXK][MAXK],s[MAXK],k;
bool vis[MAXK];
void dijkstra(int src)
{
    memset(dist,0x3f,sizeof(dist));
    memset(vis,false,sizeof(vis));
    dist[src]=0;
    for(int i=1; i<=k; ++i)
    {
        pair<int,int> tmp=make_pair(INF,-1);
        for(int j=1; j<=k; ++j)
            if(!vis[j]&&dist[j]<tmp.first)
                tmp=make_pair(dist[j],j);
        if(!~tmp.second)
            break;
        vis[tmp.second]=true;
        for(int j=1; j<=k; ++j)
            dist[j]=min(dist[j],tmp.first+pile[tmp.second][j]);
    }
}
int main()
{
    int n,m,v,x,y,d;
    while(~scanf("%d%d%d",&n,&m,&v))
    {
        city.init();
        while(m--)
        {
            scanf("%d%d%d",&x,&y,&d);
            city.add(x,y,d);
            city.add(y,x,d);
        }
        scanf("%d",&k);
        for(int i=1; i<=k; ++i)
            scanf("%d",&s[i]);
        s[++k]=1;
        s[++k]=n;
        memset(pile,0x3f,sizeof(pile));
        for(int i=1; i<=k; ++i)
            pile[i][i]=0;
        for(int i=1; i<=k; ++i)
        {
            spfa(s[i]);
            for(int j=i+1; j<=k; ++j)
            {
                int d=dist[s[j]];
                if(d>v*5)
                    d=INF;
                pile[i][j]=pile[j][i]=d;
            }
        }
        dijkstra(k-1);
        printf("%d\n",dist[k]==INF?-1:dist[k]);
    }
}

  E. 多层礼包

  水题,对栈的复习,不用栈也能做,不再多说。

#include<cstdio>
#include<stack>
using namespace std;
char str[105];
int main()
{
    while(~scanf("%s",&str))
    {
        stack<char> s;
        for(int i=0; str[i]!='G'; ++i)
            switch(str[i])
            {
            case '[':
                s.push(str[i]);
                break;
            case ']':
                if(!s.empty())
                    s.pop();
                break;
            }
        printf("%d\n",s.size());
    }
}
#include<cstdio>
using namespace std;
char str[105];
int main()
{
    while(~scanf("%s",&str))
    {
        int ans=0;
        for(int i=0; str[i]!='G'; ++i)
            switch(str[i])
            {
            case '[':
                ++ans;
                break;
            case ']':
                if(ans>0)
                    --ans;
                break;
            }
        printf("%d\n",ans);
    }
}

  F. 无线网络

  这道题的难度在于思路。

题目给出了全部点的坐标。然后非常多人就不知道这是一道什么类型的题了;实质上是给出了一个全然图。然后我们注意到。实现随意寝室的互联,并不须要直接互联。换言之,随意寝室的互联只是寻找一个最小生成树。

然后是怎样处理电力猫的问题,我们注意到电力猫相当于超级版的路由器。实现路由器的功能又不考虑距离,所以如果有k个电力猫,实质上我们是在找最小生成树的第k大边的长度。更具体的讨论能够看POJ原题的Discuss。更具体的证明须要讨论最小生成树和连通分量的关系,能够看这篇文章的第11~12页。此外,这道题是一个全然图。换言之是个非常稠密的图。用prim是比kruskal好得多的选择,尽管我控制了数据量让它们都能过。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int MAXN=505;
const int INF=0x3f3f3f3f;
bool vis[MAXN];
double g[MAXN][MAXN],lowc[MAXN],len[MAXN];
int n,k,x[MAXN],y[MAXN];
inline int sqr(int x)
{
    return x*x;
}
double prim()
{
    memset(vis,false,sizeof(vis));
    for(int i=1; i<=n; ++i)
        lowc[i]=g[1][i];
    vis[1]=true;
    int cnt=0;
    for(int i=1; i<n; ++i)
    {
        int mark=-1;
        double minc=INF;
        for(int j=1; j<=n; ++j)
            if(!vis[j]&&minc>lowc[j])
            {
                minc=lowc[j];
                mark=j;
            }
        if(!~mark)
            return -1;
        len[cnt++]=minc;
        vis[mark]=true;
        for(int j=1; j<=n; ++j)
            if(!vis[j]&&lowc[j]>g[mark][j])
                lowc[j]=g[mark][j];
    }
    sort(len,len+cnt);
    for(int i=0;i<cnt;++i)
        printf("%.2f ",len[i]);
    putchar('\n');
    return len[cnt-k];
}
int main()
{
    while(~scanf("%d%d",&n,&k))
    {
        for(int i=1; i<=n; ++i)
            scanf("%d%d",&x[i],&y[i]);
        for(int i=1; i<=n; ++i)
            for(int j=i; j<=n; ++j)
                g[i][j]=g[j][i]=sqrt(sqr(x[i]-x[j])+sqr(y[i]-y[j]));
        printf("%.2f\n",prim());
    }
}
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int MAXN=505;
const int MAXM=MAXN*MAXN;
inline int sqr(int x)
{
    return x*x;
}
struct edge
{
    int u,v;
    double w;
    edge(int _u=0,int _v=0,double _w=0):u(_u),v(_v),w(_w) {}
    bool operator<(const edge &oth) const
    {
        return w<oth.w;
    }
} e[MAXM];
int n,m,k,x[MAXN],y[MAXN],u[MAXN];
void init()
{
    for(int i=1; i<=n; ++i)
        u[i]=i;
}
int find(int x)
{
    if(u[x]!=x)
        u[x]=find(u[x]);
    return u[x];
}
void merge(int x,int y)
{
    u[find(x)]=find(y);
}
double kruskal()
{
    if(n-k==0)
        return 0;
    sort(e,e+m);
    int cnt=0;
    init();
    for(int i=0; i<m; ++i)
        if(find(e[i].u)!=find(e[i].v))
        {
            merge(e[i].u,e[i].v);
            if(++cnt==n-k)
                return e[i].w;
        }
}
int main()
{
    while(~scanf("%d%d",&n,&k))
    {
        for(int i=1; i<=n; ++i)
            scanf("%d%d",&x[i],&y[i]);
        m=0;
        for(int i=1; i<=n; ++i)
            for(int j=i+1; j<=n; ++j)
            {
                double l=sqrt(sqr(x[i]-x[j])+sqr(y[i]-y[j]));
                e[m++]=edge(i,j,l);
                e[m++]=edge(j,i,l);
            }
        printf("%.2f\n",kruskal());
    }
}

  G. barty的智商

  首先说句题外话。barty是北航有史以来第一支也是眼下唯一一支ACM Final队的成员,是GG的队友,专攻图论。这道题也是以前的一道校赛原题,然而它并不难。它突出的思想是怎样把一个求解性问题转化成验证性问题。对于这道题。我们寻找满足要求的最低智商是困难的。但对于某一个智商值,我们推断它是否满足要求是easy的,仅仅要跑一遍拓扑排序就能够了。于是能够产生二分答案的思想,就是在智商值的范围内进行二分,对于每个值进行验证;二分过程和B题极其相似,也是在找一个下界(从而也能够看出对于二分,像书上单纯的查找某个值是否存在是最基础的,而B题的二分方式相同是有意义的)。

  验证的时候能够选择建立新图,这里使用了vector模拟邻接表。

#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=10005;
const int INF=0x3f3f3f3f;
vector<pair<int,int> > G[MAXN];
vector<int> g[MAXN];
int du[MAXN],n;
bool toposort()
{
    memset(du,0,sizeof(du));
    for(int i=1; i<=n; ++i)
        for(int j=0; j<g[i].size(); ++j)
            ++du[g[i][j]];
    int tot=0;
    queue<int> q;
    for(int i=1; i<=n; ++i)
        if(!du[i])
            q.push(i);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        ++tot;
        for(int i=0; i<g[u].size(); ++i)
        {
            int v=g[u][i];
            if(!(--du[v]))
                q.push(v);
        }
    }
    return tot==n;
}
int main()
{
    int t,m,a,b,c;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1; i<=n; ++i)
            G[i].clear();
        while(m--)
        {
            scanf("%d%d%d",&a,&b,&c);
            G[b].push_back(make_pair(a,c));
        }
        int l=0,r=INF;
        while(l<r)
        {
            int m=l+r>>1;
            for(int i=1; i<=n; ++i)
            {
                g[i].clear();
                for(int j=0; j<G[i].size(); ++j)
                    if(G[i][j].second>m)
                        g[i].push_back(G[i][j].first);
            }
            toposort()?r=m:l=m+1;
        }
        printf("%d\n",l);
    }
}

  也能够不建新图。对拓扑排序稍作修改。用前向星来写的邻接表。

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int MAXN=10005;
const int MAXM=10005;
const int INF=0x3f3f3f3f;
struct graph
{
    int head[MAXN];
    int to[MAXM];
    int next[MAXM];
    int len[MAXM];
    int tot;
    void init()
    {
        tot=0;
        memset(head,0xff,sizeof(head));
    }
    void add(int u,int v,int w=-1)
    {
        to[tot]=v;
        len[tot]=w;
        next[tot]=head[u];
        head[u]=tot++;
    }
} g;
int du[MAXN],n;
bool toposort(int k)
{
    memset(du,0,sizeof(du));
    for(int i=1; i<=n; ++i)
        for(int j=g.head[i]; ~j; j=g.next[j])
            if(g.len[j]>k)
                ++du[g.to[j]];
    int tot=0;
    queue<int> q;
    for(int i=1; i<=n; ++i)
        if(!du[i])
            q.push(i);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        ++tot;
        for(int i=g.head[u]; ~i; i=g.next[i])
            if(g.len[i]>k)
            {
                int v=g.to[i];
                if(!(--du[v]))
                    q.push(v);
            }
    }
    return tot==n;
}
int main()
{
    int t,m,a,b,c;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        g.init();
        while(m--)
        {
            scanf("%d%d%d",&a,&b,&c);
            g.add(a,b,c);
        }
        int l=0,r=INF;
        while(l<r)
        {
            int m=l+r>>1;
            toposort(m)?r=m:l=m+1;
        }
        printf("%d\n",l);
    }
}

  总的来说,我想给全部坚持独立完毕题目的童鞋点个赞。这次的题目可做性非常强,但也确实非常有难度,坚持做下来的人非常值得鼓舞。期末考试临近,希望大家好好复习重点的数据结构和算法,期末上机和笔试加油~(尽管上机是我们出题,写我们的外表似参加平地机or合分……)

版权声明:本文博客原创文章,博客,未经同意,不得转载。

posted @ 2015-08-06 18:55  mengfanrong  阅读(203)  评论(0编辑  收藏  举报