2014年北邮网研机试

文章目录

  • Problem A. 分数加法
    • 题目描述
    • 输入格式
    • 输出格式
    • 输入样例
    • 输出样例
    • AC代码
      • 使用gcd化为最简分数
      • 判断ab是否相等,绕过gcd直接化简
      • 知识点补充:gcd
  • Problem B. 最小堆
    • 题目描述
    • 输入格式
    • 输出格式
    • 输入样例
    • 输出样例
    • AC代码
      • 建立二叉树递归判断法
      • 并查集法
  • Problem C. 进程管理
    • 题目描述
    • 输入格式
    • 输出格式
    • 输入样例
    • 输出样例
    • AC代码
      • 双亲表示法
  • Problem D. 网络传输
    • 题目描述
    • 输入格式
    • 输出格式
    • 样例输入
    • 样例输出
    • 样例说明
    • AC代码
      • 邻接表+dijkstra(无堆优化)+排序树dfs剪枝
      • 邻接表+dijkstra(堆优化)+排序树dfs剪枝
    • TLE代码

Problem A. 分数加法

https://.bupt.edu.cn/http/10.105.242.80/problem/p/266/

题目描述

求2-a + 2-b,其中a和b均为正整数,结果请用最简分数表示。

输入格式

第一行为测试数据的组数T(1<=T<=400)。请注意,任意两组测试数据之间是相互独立的。
每组测试数据一行,包含两个整数a和b(2<=a,b<=20)。

输出格式

对于每组测试数据,在一行内输出结果,分子和分母用“/”隔开。

输入样例

2
2 4
3 2

输出样例

5/16
3/8

AC代码

使用gcd化为最简分数

#include 
#define FF(a,b) for(int a=0;a
#define F(a,b) for(int a=1;a<=b;a++)
#define LEN 100
#define INF 1000000
#define bug(x) cout<<#x<<"="<

using namespace std;
typedef long long ll;
const double pi=acos(-1);

void FractionAdd(int a_,int b_,ll & u,ll & v){//ans=u/v
    int a=min(a_,b_);
    int b=max(a_,b_);
    u=v=(ll)1;
    v<<=b;
    u<<=(b-a);
    u++;
}

int main()
{
    freopen("./in","r",stdin);
    int T,a,b;
    ll u,v;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&a,&b);
        FractionAdd(a,b,u,v);
        ll d=__gcd(u,v);
        printf("%lld/%lld\n",u/d,v/d);
    }
    return 0;
}

判断ab是否相等,绕过gcd直接化简

此方法需要对一些特例进行特判,相比较上法较为复杂。不过能避免一次位运算,在某些样例中能避免溢出。

#include 
#define FF(a,b) for(int a=0;a
#define F(a,b) for(int a=1;a<=b;a++)
#define LEN 100
#define INF 1000000
#define bug(x) cout<<#x<<"="<

using namespace std;
typedef long long ll;
const double pi=acos(-1);

void FractionAdd(int a_,int b_,ll & u,ll & v){//ans=u/v
    if(a_-b_){
        int a=min(a_,b_);
        int b=max(a_,b_);
        u=v=(ll)1;
        v<<=b;
        u<<=(b-a);
        u++;
    }else{  //a=b
        u=v=1;
        v<<=(a_-1);
    }

}

int main()
{
//    freopen("./in","r",stdin);
    int T,a,b;
    ll u,v;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&a,&b);
        if(!a&&!b){ //a=b=0 特例
            puts("2/1");continue;
        }
        FractionAdd(a,b,u,v);
        printf("%lld/%lld\n",u,v);
    }
    return 0;
}

知识点补充:gcd

int gcd(int a,int b){
    while( b != 0){
        int t = a % b;
        a = b;
        b = t;
    }
    return a;
}

Problem B. 最小堆

https://.bupt.edu.cn/http/10.105.242.80/problem/p/267/

题目描述

给定一棵带权二叉树,请判断它是不是一个最小堆。
一棵二叉树是一个最小堆,当且仅当对于树上任意一个节点,它的权值都小于或等于以它为根的子树中的所有权值。

输入格式

输入数据第一行是一个整数T(1<=T<=100),表示测试数据的组数。
对于每组测试数据:
第一行是一个整数N(1<=N<=100),表示树的节点个数。
接下来一行包含N个正整数,第i个整数valuei(1<=valuei<=1000)表示编号i的点的权值。
接下来N-1行,每行两个整数u和v(1<=u,v<=N, u!=v),表示节点u是节点v的父节点。
测试数据保证给定的一定是一棵二叉树,并且节点1是树的根结点。

输出格式

对于每组测试数据,如果给定的树是一个最小堆则输出Yes,否则输出No。

输入样例

3
1
10
3
10 5 3
1 2
1 3
5
1 2 3 4 5
1 3
1 2
2 4
2 5

输出样例

Yes
No
Yes

AC代码

建立二叉树递归判断法

#include 
#define FF(a,b) for(int a=0;a
#define F(a,b) for(int a=1;a<=b;a++)
#define LEN 100
#define INF 1000000
#define bug(x) cout<<#x<<"="<

using namespace std;
typedef long long ll;
const double pi=acos(-1);

int w[110];
int in[110];
typedef struct Node{
    int i;
    vector<int> c;
}Node;
Node tree[110];

bool judge(int r){
    FF(i,tree[r].c.size()){
        int c=tree[r].c[i];
        if(w[r]>w[c] || judge(c)==0)
            return 0;
    }
    return 1;
}

int main()
{
//    freopen("./in","r",stdin);
    int T,N,a,b;
    scanf("%d",&T);
    memset(in,0,sizeof in);
    while(T--){
        scanf("%d",&N);
        F(i,N){
            scanf("%d",&w[i]);
            tree[i].c.clear();//不初始化就wa
        }
        F(i,N-1){
            scanf("%d%d",&a,&b);
            tree[a].c.push_back(b);
            in[b]++;
        }
        int root=1;
        puts(judge(root)?"Yes":"No");
    }
    return 0;
}

并查集法

reference:https://blog.csdn.net/u012963208/article/details/62044980
不过感觉法2的时间复杂度比法1还高…

Problem C. 进程管理

https://.bupt.edu.cn/http/10.105.242.80/problem/p/268/

题目描述

在操作系统中,进程管理是非常重要的工作,每个进程都有唯一的进程标识(PID)。每个进程都可以启动子进程,此时我们称它为其子进程的父进程,除了PID为0的进程之外,每个进程有且只有一个父进程,在这个任务中,你需要实时维护操作系统运行中的三个基本操作:

  1. FORK PID1 PID2:标识为PID1的进程启动了一个标识为PID2的子进程。
  2. KILL PID:结束标识为PID的进程。请注意,与此同时所有PID的子进程也将同时结束。如果PID是不存在或已经结束的进程,则不做任何操作。
  3. QUERY PID:查询标识为PID的进程是否仍然存在。
    在初始状态下,系统只开启了PID为0的进程,并且在任何情况下该进程不会结束。

输入格式

输入的第一行是一个整数T(T<=50),表示输入的数据组数。
每组测试数据的第一行是一个整数N(1<=N<=100),表示操作的数量。
每下来N行,每行按照上面的描述给出每个操作,输入保证所有的进程的PID都不相同,且一个进程结束后不会被重新启动,所有PID都是[1,100]之间的整数。

输出格式

对于每次QUERY查询,如果进程存在,输出Yes,不存在则输出No

输入样例

2
5
FORK 0 1
QUERY 1
KILL 1
QUERY 1
QUERY 2
1
QUERY 0

输出样例

Yes
No
No
Yes

AC代码

双亲表示法

想写孩子双亲表示的,一直wa,无解。
此法比较暴力,待优化

#include 
#define FF(a,b) for(int a=0;a
#define F(a,b) for(int a=1;a<=b;a++)
#define LEN 110
#define INF 1000000
#define bug(x) cout<<#x<<"="<

using namespace std;
typedef long long ll;
const double pi=acos(-1);

int fa[LEN];

void kill(int u){
    fa[u]=-1;
    FF(i,LEN)if(fa[i]==u)kill(i);
}

int main()
{
//    freopen("./in","r",stdin);
    int T,N;
    int a,b;
    char op[10];
    scanf("%d",&T);
    while(T--){
        scanf("%d",&N);
        memset(fa,-1,sizeof fa);
        fa[0]=0;
        while(N--){
            scanf("%s%d",op,&a);
            switch(op[0]){
                case 'F':
                    scanf("%d",&b);
                    fa[b]=a;
                    break;
                case 'K':
                    kill(a);
                    break;
                case 'Q':
                    puts(fa[a]>=0?"Yes":"No");
                    break;
            }
        }
    }
    return 0;
}

Problem D. 网络传输

https://.bupt.edu.cn/http/10.105.242.80/problem/p/269/

题目描述

网络的高效互联与智能传输是提升海量用户服务请求映射效率的重要措施。在这个任务中,你要用最少的传输时间,将特定的数据源发送到指定的网络节点中。
我们给定的网络一共包含N个节点(从1到N编号),其中节点1为数据源。网络中有M条无向边(u,v,w),表示一条传输线连接节点u和节点v,且数据通过这条传输线的平均时间为w。由于传送机制的限制,当一个节点接收到数据之后,它只能选择与它互连的一个节点,并将数据转发到该节点。节点1在初始化时只会发送一次数据,但在传输过程中它可以作为转发节点。
网络中有k个目标节点,你需要计算出该数据从节点1传送到所有K个节点所需要的最短时间。注意目标节点可以按任意顺序进行传送,数据也可以多次经过同一节点。

输入格式

输入数据第一行是一个整数T(T<=5),表示测试数据的组数。
对于每组测试数据:
第一行是三个正整数N,M,K(2<=N<=1000,1<=M<=N(N-1)/2,K<=10),分别表示节点数,边数和目标节点数。
接下来M行,每行三个整数u,v,w(1<=u,v<=N, 0<=w<=1000,u!=v)。如上所述给出每条传输线。任意两个网络节点之间最多只会有一条边相连。
最后一行是K个整数,给出所有的目标节点的编号,所有目标节点的编号都在2到N之间。

输出格式

对于每组测试数据,输出数据传送到所有K个目标节点的最短时间。

样例输入


2
3 2 2
1 3 1
1 2 3
2 3
6 6 4
1 5 1
5 6 2
2 1 20
2 3 5
3 4 5
6 3 1
2 3 4 6

样例输出

5
19

样例说明

在第一组样例中,最短路线是:1->3->1->2
在第二组样例中,最短路线是:1->5->6->3->2->3->4,或者1->5->6->3->4->3->2


思路:https://blog.csdn.net/u012963208/article/details/64457190?utm_source=blogxgwz4

AC代码

邻接表+dijkstra(无堆优化)+排序树dfs剪枝

时间:900ms

#include 
#define FF(a,b) for(int a=0;a
#define F(a,b) for(int a=1;a<=b;a++)
#define LEN 110
#define INF 2000
#define bug(x) cout<<#x<<"="<

using namespace std;
typedef long long ll;
const double pi=acos(-1);

#define VN 1100
#define EN 31000


int via[VN];//途经点
int dist[VN][VN];//dist[0]
int vis[VN];
int N,M,K;
typedef struct edge{
    int to,w;
    edge(int to,int w):to(to),w(w){}
}edge;
vector<edge> g[VN];

int dijkstra(int s){
    memset(vis,0,sizeof vis);
    fill(dist[s],dist[s]+N+1,INF);//无N+1导致wa
    dist[s][s]=0;
    //优先队列初始化
    while(1){     //优先队列非空
        int u=-1,min_d=INF;
        F(i,N){
            if(vis[i]==0 && dist[s][i]<min_d){
                u=i;
                min_d=dist[s][i];
            }
        }
        if(u==-1) break;//找到最小u点
        vis[u]=1;   //找到最近点,加入S集
        for(int i=0;i<g[u].size();i++){     //链式前向星遍历
            int to=g[u][i].to;    //链式前向星: u 的后继点 to
            int w=g[u][i].w;      //链式前向星: u -> to 边权
            if(!vis[to]){   //松弛
                if(dist[s][to] > dist[s][u]+w) { //s->to > s->u->to
                    dist[s][to] = dist[s][u] + w;
                }
            }
        }
    }
}

int sum=0,ans=0x7FFFFFFF;

void solve(int t){//对via进行全排列
    if(t>K){
        ans=min(sum,ans);
    }else{
        for(int i=t;i<=K;i++){
            swap(via[t],via[i]);
            sum+=dist[via[t-1]][via[t]];
            if(sum<ans) solve(t+1);
            sum-=dist[via[t-1]][via[t]];
            swap(via[t],via[i]);
        }
    }
}


int main()
{
//    freopen("./in","r",stdin);
    int T;
    int u,v,w;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d",&N,&M,&K);
        F(i,N)
            g[i].clear();
        while(M--){
            scanf("%d%d%d",&u,&v,&w);
            g[u].push_back(edge(v,w));
            g[v].push_back(edge(u,w));
        }
        via[0]=1;
        dijkstra(1);
        F(i,K){
            scanf("%d",via+i);
            dijkstra(via[i]);//仅仅需要计算途经点到其余点的最短距离
        }
        sum=0,ans=0x7FFFFFFF;
        solve(1);
        printf("%d\n",ans);
    }
    return 0;
}

邻接表+dijkstra(堆优化)+排序树dfs剪枝

时间:800ms
需要注意的是,对于小数据量,堆优化起得作用不大。本题大数据量的确发挥了作用,加速了100ms
非常值得注意的是,在松弛判断语句中:
2014年北邮网研机试_第1张图片
红框内必须使用>=,否则WA

#include 
#define FF(a,b) for(int a=0;a
#define F(a,b) for(int a=1;a<=b;a++)
#define LEN 110
#define INF 2000
#define bug(x) cout<<#x<<"="<

using namespace std;
typedef long long ll;
const double pi=acos(-1);

#define VN 1100
#define EN 31000


int via[VN];//途经点
int dist[VN][VN];//dist[0]
int vis[VN];
int N,M,K;
typedef struct edge{
    int to,w;
    edge(int to,int w):to(to),w(w){}
}edge;
vector<edge> g[VN];

//---------------------堆优化----------------------------
int cur;
struct cmp{
    bool operator () (int a,int b){
        return dist[cur][a]>dist[cur][b];
    }
};
priority_queue<int,vector<int>,cmp> pq;
//---------------------堆优化----------------------------

int dijkstra(int s){
    cur=s;//用于优先队列比较
    memset(vis,0,sizeof vis);
    fill(dist[s],dist[s]+N+1,INF);//无N+1导致wa
    dist[s][s]=0;
    //优先队列初始化
//    pq=priority_queue,cmp>();//定义粘贴过来强行初始化。也可以pop到空初始化
    while(!pq.empty())
        pq.pop();
    pq.push(s);
    //dijkstra主循环
    while(!pq.empty()){     //优先队列非空
        int u=pq.top();
        pq.pop();
        if(vis[u]) continue;//排除已访问点
        vis[u]=1;   //找到最近点,加入S集
        for(int i=0;i<g[u].size();i++){     //链式前向星遍历
            int to=g[u][i].to;    //链式前向星: u 的后继点 to
            int w=g[u][i].w;      //链式前向星: u -> to 边权
            if(!vis[to]){   //松弛
                if(dist[s][to] >= dist[s][u]+w) { //s->to > s->u->to
                    dist[s][to] = dist[s][u] + w;
                    pq.push(to);    //没有就wa
                }
            }
        }
    }
}

int sum=0,ans=0x7FFFFFFF;

void solve(int t){//对via进行全排列
    if(t>K){
        ans=min(sum,ans);
    }else{
        for(int i=t;i<=K;i++){
            swap(via[t],via[i]);
            sum+=dist[via[t-1]][via[t]];
            if(sum<ans) solve(t+1);
            sum-=dist[via[t-1]][via[t]];
            swap(via[t],via[i]);
        }
    }
}


int main()
{
//    freopen("./in","r",stdin);
    int T;
    int u,v,w;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d",&N,&M,&K);
        F(i,N)
            g[i].clear();
        while(M--){
            scanf("%d%d%d",&u,&v,&w);
            g[u].push_back(edge(v,w));
            g[v].push_back(edge(u,w));
        }
        via[0]=1;
        dijkstra(1);
        F(i,K){
            scanf("%d",via+i);
            dijkstra(via[i]);//仅仅需要计算途经点到其余点的最短距离
        }
        sum=0,ans=0x7FFFFFFF;
        solve(1);
        printf("%d\n",ans);
    }
    return 0;
}

TLE代码

另外,我最开始是用链式前向星写得本题。但是一直都是TLE。如果智慧的读者您发现了我代码中的错误,麻烦您留言告诉我,谢谢!

#include 
#define FF(a,b) for(int a=0;a
#define F(a,b) for(int a=1;a<=b;a++)
#define LEN 110
#define INF 2000
#define bug(x) cout<<#x<<"="<

using namespace std;
typedef long long ll;
const double pi=acos(-1);

#define VN 1100
#define EN 31000


int via[VN];//途经点
int dist[VN][VN];//dist[0]
int vis[VN];
int cur;
int N,M,K;

//---------------------链式前向星----------------------------
int head[VN];    //记录源点u在mp中第一个地址i=head[u] 调用完之后就可以用mp[i]访问边表mp
int cnt=0;        //边表下标,随着数据的录入而扩张
struct edge{    //边
    int to,next,w;
};
edge mp[EN*2];    //边表
void add(int u,int v,int w){    //增加边
    mp[cnt].to=v;
    mp[cnt].w=w;
    mp[cnt].next=head[u];    //指向源点u所构成的静态链表的头结点。如果是首次构造链,head[u]=-1 ,相当于NULL
    head[u]=cnt++;            //更新当前地址
}
void init_mp(){
    memset(head,-1,sizeof head);
    cnt=0;
}
//---------------------链式前向星----------------------------
//---------------------堆优化----------------------------
struct cmp{
    bool operator () (int a,int b){
        return dist[cur][a]>dist[cur][b];
    }
};
priority_queue<int,vector<int>,cmp> pq;
//---------------------堆优化----------------------------


void dijkstra(int s){//dist[2]
    cur=s;
    memset(vis,0,sizeof vis);
    fill(dist[s],dist[s]+N+1,INF);//无N+1导致wa
    dist[s][s]=0;
    while(!pq.empty())
        pq.pop();
    pq.push(s);     //优先队列初始化
    while(!pq.empty()){     //优先队列非空
        int u=pq.top();     //找到最小u点
        pq.pop();
        if(vis[u]) continue;//找到最小u点
        vis[u]=1;   //找到最近点,加入S集
        for(int i=head[u];~i;i=mp[i].next){     //链式前向星遍历
            int to=mp[i].to;    //链式前向星: u 的后继点 to
            int w=mp[i].w;      //链式前向星: u -> to 边权
            if(!vis[to]){   //松弛
                if(dist[s][to] > dist[s][u]+w) { //s->to > s->u->to
                    dist[s][to] = dist[s][u] + w;
                    pq.push(to);//无此,wa
                }
            }
        }
    }
}

int calc_w(){
    int res=0;
    for(int i=1;i<=K;i++){
        res+=dist[via[i-1]][via[i]];
    }
    return res;
}

void solve(int t){//对via进行全排列

}

int main()
{
//    freopen("./in","r",stdin);
    int T;
    int u,v,w;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d",&N,&M,&K);
        init_mp();      //对于每个样例,必须要重新初始化一次前向星
        while(M--){
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
            add(v,u,w);
        }
        via[0]=1;
        dijkstra(1);
        F(i,K){
            scanf("%d",via+i);
            dijkstra(via[i]);//仅仅需要计算途经点到其余点的最短距离
        }
        int ans=0x7FFFFFFF;//dist[3]
        do{
            ans=min(ans,calc_w());
            ans=ans;
        }while(next_permutation(via+1,via+K+1));//最多10个K,计算量为10!=3628800
        printf("%d\n",ans);
    }
    return 0;
}

你可能感兴趣的:(OJ刷题)