比赛题目训练系列10 (2020ICPC·小米 网络选拔赛第一场)

比赛题目训练系列10 (2020ICPC·小米 网络选拔赛第一场)

训练网址

A. Intelligent Warehouse

  • 题意:求给定集合的偏序集的最长链,偏序关系为整除。
  • 动态规划。其实就是第一层枚举 a i a_i ai,第二层枚举倍数。但是这个题卡常好严重。
  • 忘掉了忘掉了,用素数优化呀。只需要找倍数为素数就可以了。
  • 有一个地方非常需要小心,就是如果用素数的话,那么就不可以加上 if(!cnt[i]) continue;. 因为枚举素数的话,比如x的4倍就枚举不到了,那么需要两次2倍才可以传过去。这个时候 f ( 2 ∗ i ) f(2*i) f(2i) 就有用了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 10000010;
int f[maxn], cnt[maxn];
int prime[maxn], sz;
bool st[maxn];
int N;
void sieve(int N)
{
    for(int i = 2; i <= N; i++){
        if(!st[i]) prime[sz++] = i;
        for(int j = 0; prime[j] <= N / i; j++){
            st[i * prime[j]] = true;
            if(i % prime[j] == 0) break;
        }
    }
}
int main()
{
    sieve(maxn - 1);
    scanf("%d", &N);
    int x;
    for(int i = 0; i < N; i++){
        scanf("%d", &x);
        ++cnt[x];
        ++f[x];
    }
    int ans = 0;

    for(int i = 1; i <= 10000000; i++){
        //if(!cnt[i]) continue;
        for(int j = 0; j < sz && (ll)prime[j] * i <= 10000000; j++){
            int p = prime[j];
            f[p * i] = max(f[i * p], f[i] + cnt[i * p]);
        }
        ans = max(ans, f[i]);
    }
    printf("%d\n", ans);
    return 0;
}

B. Intelligent Robot

  • 二维平面,给起点和终点,平面上有一些障碍,一个机器人从起点到终点需要走的最短距离是多少?可以接触障碍的端点也可以沿着障碍走,但是不能跨过障碍。
  • 这个题和2020南京站的那个滑雪的题目很相似,都是沿着端点走,一定是最小值。那么可以暴力判断点两两之间有没有障碍。然后建图后跑一个dijkstra即可。
  • dijkstra 边权即使是浮点数,即使边权有0,也不会超时。
  • 数组开小会引起超时!
  • 函数无返回值会引起非常莫名其妙的错误,比如加一个printf之后就会段错误(一部分是编译优化的引起的)
#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;
const int maxn = 610, maxm = 400000;
typedef pair<double, double> P;
const double eps = 1e-8, INF = 1e18;

int sign(double x)
{
    if(fabs(x) < eps) return 0;
    if(x < 0) return -1;
    return 1;
}
P operator+(P a, P b)
{
    return {a.x + b.x, a.y + b.y};
}
P operator-(P a, P b)
{
    return {a.x - b.x, a.y - b.y};
}
double operator *(P a, P b)
{
    return a.x * b.x + a.y * b.y;
}

double cross(P a, P b)
{
    return a.x * b.y - a.y * b.x;
}

bool intersection(P a1, P a2, P b1, P b2)
{
    double c1 = cross(a2 - a1, b1 - a1), c2 = cross(a2 - a1, b2 - a1);
    double c3 = cross(b2 - b1, a2 - b1), c4 = cross(b2 - b1, a1 - b1);
    //下面不可以写成 <=
    return sign(c1) * sign(c2) < 0 && sign(c3) * sign(c4) < 0;
}
double dist(P a, P b)
{
    //这个地方把 double 写成 return 了,函数无返回值导致访问了非法内存。
    double dx = a.x - b.x, dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}
int h[maxn], e[maxm], ne[maxm], idx;
double w[maxm];
int maxx, maxy, N;
P st[maxn], ed[maxn], start, goal, points[maxn];
int sz;

void add(int a, int b, double c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

bool vis[maxn];
double d[maxn];
double dijkstra(int s, int t, int n)
{
    typedef pair<double, int> PII;
    for(int i = 1; i <= n; i++) d[i] = INF;
    priority_queue<PII, vector<PII>, greater<PII>> que;
    d[s] = 0;
    que.push({0, s});
    while(que.size()){
        auto p = que.top(); que.pop();
        int u = p.y;
        if(vis[u]) continue;
        vis[u] = true;
        for(int i = h[u]; i != -1; i = ne[i]){
            int v = e[i];
            if(d[v] > d[u] + w[i]){
                d[v] = d[u] + w[i];
                que.push({d[v], v});
            }
        }
    }
    return d[t];
}
int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d%d", &maxx, &maxy, &N);
    for(int i = 1; i <= N; i++){
        scanf("%lf%lf%lf%lf", &st[i].x, &st[i].y, &ed[i].x, &ed[i].y);
        points[++sz] = st[i]; points[++sz] = ed[i];
    }

    scanf("%lf%lf%lf%lf", &start.x, &start.y, &goal.x, &goal.y);
    points[++sz] = start;
    points[++sz] = goal;
//    for(int i = 1; i <= sz; i++){
//        printf("*** %f %f\n", points[i].x, points[i].y);
//    }
    for(int i = 1; i <= sz; i++){

        for(int j = i + 1; j <= sz; j++){

            bool flag = true;
            for(int k = 1; k <= N && flag; k++){
                if(intersection(points[i], points[j], st[k], ed[k])) {
                    flag = false;
                }

            }
            if(flag) {
                double dd = dist(points[i], points[j]);
                //printf("%d %d %f\n", i, j, dd);
                add(i, j, dd), add(j, i, dd);
            }
        }
    }
    printf("%.4f\n", dijkstra(sz - 1, sz, sz));
    return 0;
}

D. Router Mesh

E. Phone Network

  • 题意:一个长度n的数组,每个数在[ 1,m ]区间内,保证了每个数(在[ 1 , m ])至少出现过一次。对于每个 i ∈ [ 1 , m ],分别输出在原数列中,包含了 [ 1 , i ] 的所有种类数字的最短区间长度。
  • 别人的题解
#include<bits/stdc++.h>
using namespace std;
const int maxn = 200010, INF = 1e9;
struct node
{
    //mi[i] 表示从i开始,包含 1~x的最短区间,的右端点。
    int l, r;
    int mi, len, lazy;
}tr[maxn * 4];
int N, M;
vector<int> w[maxn];
void pushup(int u)
{
    tr[u].mi = min(tr[2 * u].mi, tr[2 * u + 1].mi);
    tr[u].len = min(tr[2 * u].len, tr[2 * u + 1].len);
}
void pushdown(int u)
{
    int t = tr[u].lazy;
    if(t){
        tr[2 * u].mi = tr[2 * u].lazy = t, tr[2 * u].len = t - tr[2 * u].r + 1;
        tr[2 * u + 1].mi = tr[2 * u + 1].lazy = t, tr[2 * u + 1].len = t - tr[2 * u + 1].r + 1;
        tr[u].lazy = 0;
    }
}
void build(int u, int l, int r)
{
    tr[u].l = l, tr[u].r = r, tr[u].len = INF;
    tr[u].mi = tr[u].lazy = 0;
    if(l == r) return;
    int mid = (l + r) / 2;
    build(2 * u, l, mid), build(2 * u + 1, mid + 1, r);
}

void modify(int u, int l, int r, int c)
{
    //if(tr[u].r < l || tr[u].l > r) return;
    if(l <= tr[u].l && tr[u].r <= r){
        tr[u].mi = tr[u].lazy = c;
        tr[u].len = c - tr[u].r + 1;
        return;
    }
    pushdown(u);
    int mid = (tr[u].l + tr[u].r) / 2;
    if(l <= mid) modify(2 * u, l, r, c);
    if(r > mid) modify(2 * u + 1, l, r, c);
    pushup(u);
}
//找到 [l, r] 中小于 last 的最大位置。
//即 如果mi[k]<p(i),表示x+1未被包含在这个区间,所以右端点要移到p(i),即mi[k]=p(i)
//因为last - mi[k] + 1 作为以 i 为左端点len,只能作为在 [pre, cnt] 的答案。
int find(int u, int l, int r, int last)
{
    if(tr[u].mi >= last) return -1;
    if(tr[u].l == tr[u].r) return tr[u].l;
    pushdown(u);
    int mid = (tr[u].l + tr[u].r) / 2;
    int tmp = -1;
    tmp = find(2 * u + 1, l, r, last);
    if(tmp == -1 && l <= mid) tmp = find(2 * u, l, r, last);
    return tmp;
}
int main()
{
    scanf("%d%d", &N, &M);
    for(int i = 1, x; i <= N; i++){
        scanf("%d", &x);
        w[x].push_back(i);
    }
    build(1, 1, N);
    for(int i = 1; i <= M; i++){
        int pre = 1, sz = w[i].size();
        for(int j = 0; j < sz; j++){
            int last = w[i][j];
            int cnt = find(1, pre, last, last);
            if(cnt != -1) modify(1, pre, cnt, last);
            pre = last + 1;
        }
        if(w[i].back() + 1 <= N) modify(1, w[i].back() + 1, N, INF);
        printf("%d ", tr[1].len);
    }
    return 0;
}

F. Design Problemset

  • 题意:
    在这里插入图片描述

  • 思路:我们发现,假如我们确定了制作的 problemset的 数量,就可以很简单的判断出此方案是否可行。我们二分答案。首先把左端点加起来,大于 R的话方案数显然是0,右端点加起来小于 L 方案数显然是0.

  • 二分的check具体来讲,我们首先看都取左区间的话,每类题目能否不超过最大的题目数量 a [ i ] a[i] a[i] 限制(超过返回false);然后我们看都取左端点能否大于等于 L(大于等于就返回 true);最后我们这样子取,假设要制作 x 个 problemset,我们取 min ⁡ { x ∗ r [ i ] , a [ i ] } \min\{x * r[i], a[i]\} min{xr[i],a[i]},比较是否能够达到 L.

#include<bits/stdc++.h>
using namespace std;
const int maxn = 100010;
typedef long long ll;
ll L, R, N, l[maxn], r[maxn], a[maxn];
ll suml, sumr, sumr_l;

bool check(ll x)
{
    for(int i = 1; i <= N; i++) if((__int128)x * l[i] > a[i]) return false;
    if(suml >= L) return true;
    __int128 res = 0;
    for(int i = 1; i <= N; i++){
        res += min((__int128)x * r[i], (__int128)a[i]);
    }
    return res >= (__int128)x * L;
}
int main()
{
    scanf("%lld%lld%lld", &N, &L, &R);
    for(int i = 1; i <= N; i++){
        scanf("%lld", &a[i]);
    }
    for(int i = 1; i <= N; i++){
        scanf("%lld%lld", &l[i], &r[i]);
        suml += l[i], sumr += r[i], sumr_l += r[i] - l[i];
    }
    if(suml > R || sumr < L)
    {
        printf("0\n");
    }
    else{
        ll lb = 0, ub = 1e18;
        while(ub > lb){
            ll mid = (lb + ub + 1) / 2;
            //printf("%lld\n", mid);
            if(check(mid)) lb = mid;
            else ub = mid - 1;
        }
        printf("%lld\n", lb);
    }

    return 0;
}

G. Tree Projection

I. Walking Machine

  • 题意:一个地图上标着一些字母,表示在这个点要往哪个地方走。问有多少个点会走到地图外面。
  • 很简单的题,深搜就行。走到地图外面就记为0,走到一个地图里面的环上就记为1. 初始化为-1表示尚未搜到。
  • 为了方便开一个 vis 数组表示是否搜到,以及 f 数组表示这个点是什么类型的(走动地图外面还是走不到地图外面)。
#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;
typedef pair<int, int>P;
unordered_map<char, P> dir;
const int maxn = 1010;
char mz[maxn][maxn];
bool vis[maxn][maxn];
int f[maxn][maxn];
int N, M;
int dfs(int x, int y)
{
    vis[x][y] = true;
    auto p = dir[mz[x][y]];
    int nx = x + p.x, ny = y + p.y;
    if (nx < 1 || nx > N || ny < 1 || ny > M) return f[x][y] = 0;
    if(vis[nx][ny] && f[nx][ny] == -1) return f[x][y] = 1;
    else if(vis[nx][ny]) return f[x][y] = f[nx][ny];
    return f[x][y] = dfs(nx, ny);
}
int main()
{
    memset(f, -1, sizeof f);
    dir['W'] = {-1, 0}, dir['S'] = {1, 0}, dir['A'] = {0, -1}, dir['D'] = {0, 1};
    scanf("%d%d", &N, &M);
    for(int i = 1; i <= N; i++){
        scanf("%s", mz[i] + 1);
    }
    for(int i = 1; i <= N; i++){
        for(int j = 1; j <= M; j++){
            if(!vis[i][j]) f[i][j] = dfs(i, j);
        }
    }
    int ans = 0;
    for(int i = 1; i <= N; i++){
        for(int j = 1; j <= M; j++){
            ans += (f[i][j] == 0);
        }
    }
    printf("%d\n", ans);
    return 0;
}

J. Matrix Subtraction

  • 题意:给一个 n × m n\times m n×m 的矩阵。可以选中一个 a × b a\times b a×b 的子矩阵,把里面的所有数都减1(子矩阵是连续的一部分,不要记错),使得最后子矩阵的所有数都是0.
  • 一个矩阵全为零,等价于他的二维差分矩阵全为0. 因为要想使左上角元素(比如第一行第一列的元素)变成0,只能把它作为小矩形的左上角做区间减法。因此我们遍历二分差分矩阵,如果出现大于0的元素,就以它作为小矩阵的左上角,对小矩阵做区间减法。最后看看二维差分数组是否全部变成了0.
  • 有一个需要注意的地方是,遍离二维差分时,因为题目中要求只能减不能加,因此只能出现大于0的时候才减去,小于等于0的时候不可以。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1010;
typedef long long ll;
ll m[maxn][maxn];
int N, M, A, B;
void insert(int x1, int y1, int x2, int y2, ll c)
{
    m[x1][y1] += c;
    m[x2 + 1][y1] -= c;
    m[x1][y2 + 1] -= c;
    m[x2 + 1][y2 + 1] += c;
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%d%d%d%d", &N, &M, &A, &B);
        for(int i = 1; i <= N; i++){
            fill(m[i], m[i] + M + 1, 0);
        }
        for(int i = 1; i <= N; i++){
            for(int j = 1; j <= M; j++){
                int x;
                scanf("%d", &x);
                insert(i, j, i, j, x);
            }
        }
        for(int i = 1; i + A - 1 <= N; i++){
            for(int j = 1; j + B - 1 <= M; j++){
                if(m[i][j] > 0) insert(i, j, i + A - 1, j + B - 1, -m[i][j]);
            }
        }
        bool flag = true;
        for(int i = 1; i <= N && flag; i++){
            for(int j = 1; j <= M && flag; j++){
                if(m[i][j]) flag = false;
            }
        }
        if(flag) printf("^_^\n");
        else printf("QAQ\n");
    }

    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值