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

B - 合约数

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

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

#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 1 ,因为有些空一定要插入,那么这些空和汇点的连边加个流量下界(为1),对于二分答案 mid m i d 的时候跑一次有界最大流,先看是否存在可行流,不存在肯定不行,然后是存在可行流的情况下再求一次最大流,最大流量为广告个数则该值可行,否则也不可以。

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值