埃森哲杯第十六届上海大学程序设计联赛春季赛暨上海高校金马五校赛

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hnust_Derker/article/details/79951958

B - 合约数

思路:
     每个节点都有权值, 但是权值不超过10000,可以预处理出所有数的合约数,现在要计算以i为根中且节点权值是节点i权值的合约数的个数,考虑到是子树的情况,可以跑个dfs序,然后对于F(i),在节点i对应的子树区间查找vali每个合约数个数和,对于vali的约数是k的时候,就是在一个区间内查找值为k的个数。这个有两种方法,一个是线段树+二分查找, 一次查找是O(log2n),这里是过不了的,因为还要枚举因子。还有一种方法是主席树,时间是O(logn),是可以的。

#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 5e5 + 10;
const ll mod = 1000000007;
using namespace std;

int n, m, T, kase = 1, rt;
vector<int> g[maxn], divv[maxn];
int l[maxn], r[maxn], dfn[maxn];
int val[maxn], cnt, num, v[maxn];
int ls[maxn], rs[maxn], pr[maxn];
int root[maxn], C[maxn];

void dfs(int x, int fa) {
    dfn[num] = x; val[num] = v[x]; l[x] = num++;
    for(int i = 0; i < g[x].size(); i++){
        int vs = g[x][i];
        if(vs != fa) dfs(vs, x);
    }
    r[x] = num - 1;
}

void init() {
    memset(pr, 0, sizeof pr);
    for(int i = 2; i < 10010; i++) {
        //if(i < 100) cout << i << " : " << pr[i] << endl;
        if(pr[i]) continue;
        for(int j = i * i; j < 10010; j += i) pr[j] = 1;
    }
    for(int i = 4; i < 10010; i++) {
        for(int j = 1; j * j <= i; j++) {
            if(i % j) continue;
            int d = i / j;
            if(pr[j]) divv[i].push_back(j);
            if(d != j && pr[d]) divv[i].push_back(d);
        }
    }
}

void build(int o, int l, int r) {
    if(l == r) return ;
    int mid = (l + r) >> 1;
    ls[o] = cnt++; build(ls[o], l, mid);
    rs[o] = cnt++; build(rs[o], mid + 1, r);
}

void update(int lt, int &rt, int l, int r, int ind) {
    rt = cnt++; C[rt] = C[lt] + 1;
    if(l == r) return ;
    ls[rt] = ls[lt]; rs[rt] = rs[lt];
    int mid = (l + r) >> 1;
    if(ind <= mid) update(ls[lt], ls[rt], l, mid, ind);
    else update(rs[lt], rs[rt], mid + 1, r, ind);
}

int query(int o1, int o2, int l, int r, int k) {
    if(l == r) { return C[o2] - C[o1]; }
    int mid = (l + r) >> 1;
    if(k <= mid) return query(ls[o1], ls[o2], l, mid, k);
    else return query(rs[o1], rs[o2], mid + 1, r, k);
}
int b[maxn], tot;
ll F[maxn];

void init(int n) {
    for(int i = 0; i < maxn; i++) g[i].clear();
    num = 1; cnt = 0; tot = 0;
    root[0] = 0; cnt = 1;
    build(0, 1, n);
    memset(C, 0, sizeof C);
    memset(F, 0, sizeof F);
}

int main() {
    //scanf("%d", &T);
    init();
    cin >> T;
    while(T--) {
        scanf("%d %d", &n, &rt);
        init(n);
        for(int i = 0; i < n - 1; i++) {
            int u, v; scanf("%d %d", &u, &v);
            g[u].push_back(v);
            g[v].push_back(u);
        }

        for(int i = 1; i <= n; i++) { scanf("%d", &v[i]); b[tot++] = v[i]; }
        dfs(rt, 0);
        sort(b, b + tot);
        tot = unique(b, b + tot) - b;

        for(int j = 1; j < num; j++) {
            int vl = val[j];
            int c = lower_bound(b, b + tot, vl) - b + 1;
            root[j] = cnt++;
            update(root[j - 1], root[j], 1, num - 1, c);
        }

        for(int i = 1; i <= n; i++) {
            int va = v[i];
            for(int j = 0; j < divv[va].size(); j++) {
                int dv = divv[va][j];
                int ds = lower_bound(b, b + tot, dv) - b;
                if(ds == tot || b[ds] != dv) continue;
                F[i] += query(root[l[i] - 1], root[r[i]], 1, num - 1, ds + 1);
            }
        }
        ll ans = 0;
        for(int i = 1; i <= n; i++) ans = (ans + F[i] * i) % mod;
        printf("%lld\n", ans);
    }
    return 0;
}


G - 小Y做比赛

思路:
      因为是看两题做一题,可以知道, 开始一定有某种方式的替换表示看at题,看a1题,做a1题(a1表示),也就是将at题留在后面的某个地方做,先看一种简单的形式,即:at题留在最后做,那么序列形成肯定是, ata1a1a2a2...ananat,这样要使花费时间最小的话,那么首先是at最小,然后是a1+a1a2+a2...an+an,这样就保证了总罚时最小。
      现在考虑有某个题ak读了之后做了at,即序列变成这样:ata1a1a2a2...anan ak at....,根据刚才的结论,依旧是a1+a1a2+a2...an+an,这里可以对每个题目的看题时间和做题时间的和排个序,现在考虑什么时候是把ak读了,我们知道,读了ak之后做at是因为akak不能再插入an之后,为什么不能插入an之后,我们知道除了at读题时间,现在做完一道题是前面时间和该题时间总和,现在考虑到读完了ak,那么下次做的题目要么是ak要么是at,时间要么是ak+ak要么是ak+at,为了尽早交上题目,那么ak不能插入an之后就是ak+ak>ak+at,因为这个时候插入会导致后面提交时间增大,所以可以分两部分给题目时间总和和看题时间排个序然后处理一下就行了。

#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 1e5 + 10;
const ll INF = 1e9 + 10;
using namespace std;

struct P {
    ll x, y, sum, id;
    P(ll x = 0, ll y = 0, ll z = 0, ll id = 0) : x(x), y(y), sum(z), id(id) {}
    bool operator < (P p) const {
        if(sum != p.sum) return sum < p.sum;
        return id < p.id;
    }
};

struct PP {
    ll x, y, sum, id;
    PP(ll x = 0, ll y = 0, ll z = 0, ll id = 0) : x(x), y(y), sum(z), id(id) {}
    bool operator < (PP p) const {
        if(x != p.x) return x < p.x;
        return id < p.id;
    }
};

typedef pair<ll, ll> pa;
int n, m;
ll a[maxn], b[maxn];
P have[maxn], remain;
set<P> min_sum;
set<PP> min_read;

int main() {
    while(scanf("%d", &n) != EOF) {
        min_sum.clear();
        min_read.clear();
        ll ans = 0, cnt = 0, x, y, tot = 0, min_x = INF, min_id;
        for(int i = 1; i <= n; i++) {
            scanf("%lld %lld", &x, &y);
            a[i] = x; b[i] = y;
            have[i] = P(x, y, x + y);
            have[i].id = i;
            min_sum.insert(have[i]);
            min_read.insert(PP(x, y, x + y, i));
            if(x < min_x) { min_x = x; min_id = i; }
        }
        tot += have[min_id].x;
        remain = have[min_id];
        min_sum.erase(P(a[min_id], b[min_id], a[min_id] + b[min_id], min_id));
        min_read.erase(PP(a[min_id], b[min_id], a[min_id] + b[min_id], min_id));
        while(!min_sum.empty()) {
            set<P>::iterator is = min_sum.begin();
            set<PP>::iterator ir = min_read.begin();
            P sm = *is; PP rd = *ir;
            int id_s = sm.id;
            int id_r = rd.id;
            if(have[id_s].sum <= have[id_r].x + remain.y) {
                tot += have[id_s].sum; ans += tot;
                min_sum.erase(P(a[id_s], b[id_s], a[id_s] + b[id_s], id_s));
                min_read.erase(PP(a[id_s], b[id_s], a[id_s] + b[id_s], id_s));
            } else {
                tot += have[id_r].x + remain.y; ans += tot;
                min_read.erase(PP(a[id_r], b[id_r], a[id_r] + b[id_r], id_r));
                min_sum.erase(P(a[id_r], b[id_r], a[id_r] + b[id_r], id_r));
                remain = have[id_r];
            }
        }
        ans += tot + remain.y;
        cout << ans << endl;
    }
    return 0;
}


J - 小Y写文章

思路
      现在要使文章连贯值最大值最小,很容易想到二分, 那么就是判断哪些空一定要插入,广告在哪些地方不能插入,暴力判断一下,可以用网络流去做,可以每个空连向汇点流量上界为1,代表最多可以插入一次,源点向每个广告连边,流量上界为1,代表这些广告只能插入一次,然后广告和其能插入的空连边,流量上界为1,因为有些空一定要插入,那么这些空和汇点的连边加个流量下界(为1),对于二分答案mid的时候跑一次有界最大流,先看是否存在可行流,不存在肯定不行,然后是存在可行流的情况下再求一次最大流,最大流量为广告个数则该值可行,否则也不可以。

#include<bits/stdc++.h>
const int maxn = 4e2 + 10;
const int INF = 1e9 + 10;
using namespace std;

struct st {
    int to, cap, re;
    st(int t = 0, int c = 0, int r = 0) : to(t), cap(c), re(r) {}
};
int n, m, s, t;
vector<st> G[maxn];
int it[maxn], lv[maxn];

void add(int f, int t, int c) {
    G[f].push_back(st(t, c, G[t].size()));
    G[t].push_back(st(f, 0, G[f].size() - 1));
}

void bfs() {
    memset(lv, -1, sizeof(lv));
    queue<int> q;
    lv[s] = 0;
    q.push(s);
    while(!q.empty()) {
        int u = q.front(); q.pop();
        for(int i = 0; i < G[u].size(); i++) {
            st &e = G[u][i];
            if(e.cap > 0 && lv[e.to] < 0) {
                lv[e.to] = lv[u] + 1;
                q.push(e.to);
            }
        }
     }
}

int dfs(int v, int t, int f) {
    if(v == t) return f;
    for(int &i = it[v]; i < G[v].size(); i++) {
        st &e = G[v][i];
        if(e.cap > 0 && lv[v] < lv[e.to]) {
            int d = dfs(e.to, t, min(f, e.cap));
            if(d > 0) {
                e.cap -= d;
                G[e.to][e.re].cap += d;
                return d;
            }
        }
    }
    return 0;
}

int maxflow() {
    int f = 0;
    while(1) {
        bfs();
        if(lv[t] < 0) return f;
        memset(it, 0, sizeof(it));
        int fl;
        while((fl = dfs(s, t, INF)) >0) f += fl;
    }
}

int A[maxn], B[maxn];
int in[maxn], out[maxn];

bool build_graph(int mid) {
    memset(in, 0, sizeof in);
    memset(out, 0, sizeof out);
    s = 0; t = n + m + 3;
    int ss = n + m + 4, tt = ss + 1;
    for(int i = 0; i <= tt; i++) G[i].clear();
    for(int i = 1; i <= m; i++) { add(s, i, 1); }
    for(int j = 1; j <= n + 1; j++) {
        if(j > 1 && j < n + 1) {
            if(abs(A[j - 1] - A[j]) > mid)  { in[t]++; out[j + m]++; } ///流量上下界都是1,一定要填充的空位
            else add(j + m, t, 1);
        } else add(j + m, t, 1);
    }

    for(int i = 1; i <= m; i++) {
        for(int j = 1; j <= n + 1; j++) {
            if(j > 1 && j < n + 1) { ///中间点
                if(abs(B[i] - A[j - 1]) <= mid && abs(B[i] - A[j]) <= mid) add(i, j + m, 1); ///可以插入这里
            } else if(j == 1) {  ///判断头尾是否可以插入
                if(abs(B[i] - A[j]) <= mid) add(i, j + m, 1);
            } else {
                if(abs(B[i] - A[j - 1]) <= mid) add(i, j + m, 1);
            }
        }
    }
    for(int i = 0; i <= t; i++) {
        if(in[i]) add(ss, i, in[i]);
        if(out[i]) add(i, tt, out[i]);
    }
    add(t, s, INF);
    s = ss; t = tt;
    int mf = maxflow();
    for(int j = 0; j < G[ss].size(); j++) {
        if(G[ss][j].to != 0 && G[ss][j].cap) {
            return false;
        }
    }
    for(int j = m + 1; j <= n + m + 1; j++) {
        for(int k = 0; k < G[j].size(); j++) {
            st now = G[j][k];
            if(now.to == t && now.cap) return false;
        }
    }
    s = 0; t = n + m + 3;
    ss = n + m + 4; tt = ss + 1;
    G[t].pop_back(); G[s].pop_back();
    for(int i = t; i >= 0; i--) {
        if(out[i]) { G[i].pop_back(); G[tt].pop_back(); }
        if(in[i]) { G[ss].pop_back(); G[i].pop_back(); }
    }
    for(int j = 1; j <= n + 1; j++) {
        if(j > 1 && j < n + 1) {
            if(abs(A[j - 1] - A[j]) > mid)  add(j + m, t, 1);
        }
    }
    mf += maxflow();
    if(mf != m) return false;
    else return true;
}

int main() {
    int T; scanf("%d", &T);
    while(T--) {
        scanf("%d %d", &n, &m);
        for(int i = 1; i <= n; i++) scanf("%d", &A[i]);
        for(int i = 1; i <= m; i++) scanf("%d", &B[i]);
        int l = 0, r = INF;
        while(l < r) {
            int mid = (l + r) >> 1;
            if(build_graph(mid)) r = mid;
            else l = mid + 1;
        }
        printf("%d\n", l);
    }
    return 0;
}
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页