HCPC2013校赛训练赛 2

A.素数矩阵

题意:给定一个矩阵,每次能够给矩阵中的元素加1或者是减1,问使得矩阵的某一行或者某一列满足所有元素都为素数的最少操作是多少次(没加1或者减1视作一次操作)。

解法:先把给定数字域内的素数全部筛选出来,记得稍微超过最大数,因为可能是变成一个超过最大数的素数,然后就是求出每一个数变成相邻素数的最小代价,统计出某一行或者是某一列的综合即可。

代码如下:

Problem A
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
using namespace std;

const int MaxN = 110000;
int G[505][505];
char p[110005];
int idx, rec[11000];
int N, M;

void getprime() {
    for (int i = 4; i <= MaxN; i += 2) {
        p[i] = 1;    
    }
    int limit = (int)sqrt(1.0*MaxN);
    for (int i = 3; i <= limit; i += 2) {
        int k = 2 * i;
        for (int j = i * i; j <= MaxN; j += k) {
            p[j] = 1;
        }
    }
    for (int i = 2; i <= MaxN; ++i) {
        if (!p[i]) {
            rec[idx++] = i;
        }
    }
}

int getvalue(int x) {
    int *p = lower_bound(rec, rec+idx, x);
    int *q = upper_bound(rec, rec+idx, x);
    return min(abs(x-*p), abs(*q-x));
}

int sumc[505], sumr[505];

int main() {
    getprime();
    while (cin >> N >> M) {
        memset(sumc, 0, sizeof (sumc));
        memset(sumr, 0, sizeof (sumr));
        for (int i = 1; i <= N; ++i) {
            for (int j = 1; j <= M; ++j) {
                scanf("%d", &G[i][j]);
                G[i][j] = getvalue(G[i][j]);
            }
        }
        for (int i = 1; i <= N; ++i) {
            for (int j = 1; j <= M; ++j) {
                sumc[j] += G[i][j];
                sumr[i] += G[i][j];    
            }
        }
        int Min = 0x7fffffff;
        for (int i = 1; i <= N; ++i) {
            Min = min(Min, sumr[i]);    
        }
        for (int i = 1; i <= M; ++i) {
            Min = min(Min, sumc[i]);    
        }
        printf("%d\n", Min);
    }
    return 0;    
}

C.凸多边形

题意:判定给定一个0,1矩阵中1所构成的图形是否是一个凸多边形,凸多边形的定义是,任意两个1点之间的存在一条只通过一次转弯就能连接两点的路径。

解法:首先出问题的点一定会是边界上的点,图形内部的点不能满足情况的话,边界上的点也就肯定不能。这是因为这个内部点所在的行和列上的边界点一定存在合法的点。换句话说,这些边界点如果全部合法,那么该内部点就一定合法。找出所有的边界点之后,在两两枚举。判定两个点是否连通的方法是:取两个转折点,分别取各自的行和列,这样的两个点和边界上的两个点构成一个正方形。然后查看是否存在一个点满足到两个边界点的直线上都是1即可。

代码如下:

Problem C
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
using namespace std;

int N, M, idx;
char G[55][55];

struct Point {
    int x, y;
}e[2505];

int dir[4][2] = {1, 0, -1, 0, 0, 1, 0, -1};

bool conn(int x1, int y1, int x2, int y2) {
    if (x1 == x2) {
        int sy = y1 < y2 ? y1 : y2;
        int ey = y1 + y2 - sy;
        for (int i = sy; i <= ey; ++i) {
            if (G[x1][i] == 'W') return false;
        }
        return true;
    } else {
        int sx = x1 < x2 ? x1 : x2;
        int ex = x1 + x2 - sx;
        for (int i = sx; i <= ex; ++i) {
            if (G[i][y1] == 'W') return false;
        }
        return true;
    }
}

bool judge(int x1, int y1, int x2, int y2) {
    int xa = x1, ya = y2;
    int xb = x2, yb = y1; // 只转一次弯的枢纽点,若在一条线上,同样成立
    return conn(x1, y1, xa, ya) && conn(x2, y2, xa, ya) || conn(x1, y1, xb, yb) && conn(x2, y2, xb, yb); 
}

bool deal() {
    for (int i = 0; i < idx; ++i) {
        for (int j = i+1; j < idx; ++j) {
            if (!judge(e[i].x, e[i].y, e[j].x, e[j].y)) {
                return false;
            }
        }
    }
    return true;
}

int main() {
    int flag;
    while (scanf("%d %d", &N, &M) != EOF) {
        idx = 0;
        memset(G, 'W', sizeof (G));
        for (int i = 1; i <= N; ++i) {
            scanf("%s", G[i]+1);
            for (int j = 1; j <= M; ++j) {
                if (G[i][j] == 'B') {
                    flag = 0;
                    for (int k = 0; k < 4; ++k) {
                        if (G[i+dir[k][0]][j+dir[k][1]] != 'B')    {
                            flag = 1;
                            break;
                        }
                    }
                    if (flag) {
                        e[idx].x = i, e[idx].y = j;
                        ++idx; // 把图形边上的点全部记录起来 
                    }
                }
            }
        }
        printf(deal() ? "YES\n" : "NO\n");
    }
    return 0;    
}

G.情报

题意:给定一张图,图上的点分为A和B两类,现在有一个重要情报要从A类点(所有A类点都拥有该情报)传递到所有B类点,问最少花多少时间能够让所有B点都收到情报。有几个约束条件:每一个A类点只能够送出去一份情报,一份情报也只能传递给一个B类点。

解法:首先明确一定就是情报的传递时同时进行的,因此只要求得最后一个收到情报的人能够在最短的时间内收到情报即可。那么我们首先对原图做一个floyd,计算出两两之间的最短路,然后再二分枚举重构联络图的距离上限,构造出一个只有A指向B的二分图,在对这个图进行二分匹配,看是否能够匹配所有的B点。

代码如下:

Problem G
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int MAX = 105;
const int INF = 0x3f3f3f3f;
int n, N, M, reca[MAX], recb[MAX];
int mp[MAX][MAX];
int marry[MAX];
char G[MAX][MAX];
char vis[MAX];

void floyd() {
    for (int k = 0; k < n; ++k) {
        for (int i = 0; i < n; ++i) {
            if (mp[i][k] == INF || i == k) continue;
            for (int j = 0; j < n; ++j) {
                if (mp[k][j] == INF || j == k) continue;
                if (mp[i][j] > mp[i][k] + mp[k][j]) {
                    mp[i][j] = mp[i][k] + mp[k][j];
                }
            }
        }    
    }
}

bool path(int u) 
{
    for (int i = 0; i < M; ++i) {
        if (!G[u][i] || vis[i])
            continue;
        vis[i] = 1;
        if (marry[i] == -1 || path(marry[i])) {
            marry[i] = u;
            return true;
        }
    }
    return false;
}

void build(int lim) {
    memset(G, 0, sizeof (G));
    for (int i = 0; i < N; ++i) {
        for (int j = 0; j < M; ++j) {
            if (mp[reca[i]][recb[j]] <= lim) {
                G[i][j] = 1;
            }
        }
    }
}

bool Ac() {
    int cnt = 0;
    memset(marry, 0xff, sizeof (marry));
    for (int i = 0; i < N; ++i) {
        memset(vis, 0, sizeof (vis));
        if (path(i)) {
            ++cnt;
        }
    }
    return cnt == M;
}

int bsearch(int l, int r) {
    int mid, ret;
    while (l <= r) {
        mid = (l + r) >> 1;
        build(mid);
        if (Ac()) {
            ret = mid;
            r = mid - 1;    
        } else {
            l = mid + 1;
        }
    }    
    return ret;
}

int main()
{
    while (scanf("%d", &n) == 1) {
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                scanf("%d", &mp[i][j]);
            }
        }
        scanf("%d", &N);
        for (int i = 0; i < N; ++i) {
            scanf("%d", &reca[i]);
            --reca[i];
        }
        scanf("%d", &M);
        for (int i = 0; i < M; ++i) {
            scanf("%d", &recb[i]);    
            --recb[i];
        }
        floyd();
        printf("%d\n", bsearch(0, 10000));
    }
    return 0;
}

I.七夕

题意:牛郎和织女又要再次见面了,这次他们分别在两个垂直于x轴的两个线上选择一个点,然后要求输出某一个点对要求其欧拉距离加上一些附加的距离最短在所有组合中距离最短。牛郎每次从原点走到选择的点,而织女则有一个给定的固定序列表示其走到指定点的距离。

解法:首先确定一点,如果只是单纯的两两匹配时肯定会超时的,因此我们必须找到办法使得每一个点只找到一个最优点。方法就是连接我们原点和织女选择的B点,然后求出与牛郎那一条垂直于x轴的线交点,那么最优值一定是这个交点左右的两个点。画个图自己看看,这是显然成立的。

代码如下:

Problem I
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;

const int MaxN = 100005;
int N, M, xa, xb;
int ra[MaxN], rb[MaxN];

struct Node{
    double dis;    // 作为主键进行排序  
    int A, B;    // 分别记录两边的编号,这样使得按照dis排序后编号得以保存
}e[MaxN];        // 每一个B点能够引入一个有效点

double dist(double x1, double y1, double x2, double y2) {
    return sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
}

void insert(int B, int fee) {
    // 求出B点与原点连线上,横坐标为a的纵坐标y',然后再在ra序列寻找最接近y'的点
    double yb = rb[B], ya = 1.0 * yb / xb * xa;
    int *ptr = lower_bound(ra, ra+N, ya); // 返回一个值 >= ya 的位置,如果没有则返回原数组最大的元素
    int *pre = (ptr == ra) ? ptr : ptr - 1;
    double d1 = dist(0, 0, xa, *ptr) + dist(xa, *ptr, xb, yb);
    double d2 = dist(0, 0, xa, *pre) + dist(xa, *pre, xb, yb);
    e[B].dis = fee + min(d1, d2);
    e[B].A = d1 < d2 ? ptr - ra + 1 : pre - ra + 1;
    e[B].B = B + 1;
}

int main() {
    int fee;
    while (scanf("%d%d%d%d", &N, &M, &xa, &xb) != EOF) {
        for (int i = 0; i < N; ++i) {
            scanf("%d", &ra[i]);
        }
        for (int i = 0; i < M; ++i) {
            scanf("%d", &rb[i]);
        }
        for (int i = 0; i < M; ++i) {
            scanf("%d", &fee);
            insert(i, fee);
        }
        Node Min = e[0];
        for (int i = 1; i < M; ++i) {
            if (Min.dis > e[i].dis) {
                Min = e[i];    
            }
        }
        printf("%d %d\n", Min.A, Min.B);
    }
    return 0;    
}

J.回家Ⅱ

题意:给定一个图,求一条最短路,稍有不同的是,路径上的权值是和已经经过的节点数存在线性关系的。

解法:采用spfa算法,在距离表示上引入一维信息表示已经经过的节点数,然后就是普通的动态规划了。

代码如下:

Problem J
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;

const int INF = 0x3f3f3f3f;
int N, M;

int G[205][205], dis[205][205];
char vis[205][205];

struct Node {
    int p, x;
    Node(int p_, int x_) {p = p_, x = x_;}
    Node() {}
} v;

void spfa() {
    memset(dis, 0x3f, sizeof (dis));
    memset(vis, 0, sizeof (vis));
    queue<Node>q;
    vis[1][1] = 1;
    dis[1][1] = 0;
    q.push(Node(1, 1));
    while (!q.empty()) {
        v = q.front();
        q.pop();
        vis[v.p][v.x] = 0;
        for (int i = 1; i <= N; ++i) {
            if (G[v.x][i] == INF) continue;
            if (dis[v.p+1][i] > dis[v.p][v.x] + G[v.x][i] * v.p) {
                dis[v.p+1][i] = dis[v.p][v.x] + G[v.x][i] * v.p;    
                if (!vis[v.p+1][i]) {
                    q.push(Node(v.p+1, i));
                    vis[v.p+1][i] = 1;
                }
            }
        }    
    }
}

int main() {
    int a, b, c;
    while (scanf("%d %d", &N, &M) == 2) {
        memset(G, 0x3f, sizeof (G));
        for (int i = 1; i <= M; ++i) {
            scanf("%d %d %d", &a, &b, &c);    
            G[a][b] = G[b][a] = min(G[a][b], c);
        }
        spfa();
        int Min = INF;
        for (int i = 1; i <= N; ++i) {
            Min = min(Min, dis[i][N]);
        }
        printf("%d\n", Min);
    }
    return 0;    
}
原文地址:https://www.cnblogs.com/Lyush/p/2956466.html