2018.08.21高二互测

2018.08.20 NOIp模拟赛

redbag的神题,真的很思维啊。。真的很工业啊。。

本博客大量借用redbag的博客,侵删。

第一题

​ 给你\(~n~\)个数对,要求对于每个数对取出一个数计入答案,所有另一个数组成的集合大小为\(~n~\), 最大化答案并输出, 保证数据合法。\(~1 \leq n \leq 2.5 \times 10^5~, ~1 \leq x_i, y_i \leq 10 ^ 9\)

​ 离散化数对之后对每个点对的 \(~x, ~y~\)之间连一条边,可以发现对于任意一条边都有一端作为贡献且另一端作为限制,考虑对边定向,\(~x~\)指向\(~y~\)表示\(~y~\)计入贡献且\(~x~\)为限制。因为数据合法,可以发现图是一个由__树__和__基环外套树__组成的森林。那么遍历每一个联通块:若当前是一颗树,则可以选择一个权值最大的点贡献\(~deg_u~\)次,其他所有点贡献\(~deg_u - 1~\)次;而对于基环外套树,因为边数和点数相等,所以每一个点都只能贡献\(~deg_u - 1~\)次。

code

#include<bits/stdc++.h>
#define x first
#define y second
#define mp make_pair
#define For(i, j, k) for(int i = j; i <= k; ++i)
#define Forr(i, j, k) for(int i = j; i >= k; --i)
#define Travel(i, u) for(int i = beg[u], v = to[i]; i; i = nex[i], v = to[i])
using namespace std;

inline int read() {
    int x = 0, p = 1; char c = getchar();
    for(; !isdigit(c); c = getchar()) if(c == '-') p = -1;
    for(; isdigit(c); c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    return x *= p;
}

template<typename T> inline bool chkmin(T &a, T b) { return a > b ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return a < b ? a = b, 1 : 0; }

inline void File() {
    freopen("s.in", "r", stdin);
    freopen("s.out", "w", stdout);
}

typedef long long ll;
typedef pair<int, int> PII;
const int N = 25e4 + 1e2, M = N << 1;
int n, tot, ls[M], deg[M], siz[M], t, cnt, vis[M];
int e = 1, beg[M], nex[M << 1], to[M << 1];
vector<int> vec[M]; PII P[N];

inline void add(int x, int y) {
    to[++ e] = y, nex[e] = beg[x], beg[x] = e, ++ deg[y];
    to[++ e] = x, nex[e] = beg[y], beg[y] = e, ++ deg[x];
}

inline void dfs(int u, int f, int tag) {
    vis[u] = tag, vec[tag].push_back(u), t += deg[u];
    Travel(i, u) if (v != f && !vis[v]) dfs(v, u, tag);
}

int main() {
    File();
    n = read();
    For(i, 1, n) {
        P[i].x = read(), P[i].y = read();
        ls[++ tot] = P[i].x, ls[++ tot] = P[i].y;   
    }
    sort(ls + 1, ls + 1 + tot), tot = unique(ls + 1, ls + 1 + tot) - ls - 1;
    For(i, 1, n) {
        P[i].x = lower_bound(ls + 1, ls + 1 + tot, P[i].x) - ls;
        P[i].y = lower_bound(ls + 1, ls + 1 + tot, P[i].y) - ls;
        add(P[i].x, P[i].y);
    }

    For(i, 1, n) if (!vis[i]) 
        ++ cnt, t = 0, dfs(i, 0, cnt), siz[cnt] = t >> 1;

    ll ans = 0;
    For(i, 1, cnt) {
        int sz = vec[i].size(); 
        if (sz == siz[i]) 
            for (auto v : vec[i]) ans += 1ll * (deg[v] - 1) * ls[v];
        else {
            int res = 0;
            for (auto v : vec[i]) ans += 1ll * (deg[v] - 1) * ls[v], res = max(res, ls[v]);
            ans += res;
        } 
    }

    printf("%lld\n", ans);
    return 0;
}

第二题

1416390-20180822082255681-1586812248.png

​ 数据规模\(~O(n ^ 2)~\)还可以套一个小\(~log~\),超级背包四合一,第一二问都可以直接\(~dp~\)。对于第三问,可以考虑把序列按一个奇怪的法则排序,按照作业本的课时数将作业本分成若干类。依次从小到大在每一类中取一本作业本,加入到数组里,直到所有的作业被取完,这样再跑背包,枚举第一符合条件的位置,输出其在之前出现过的次数就行了,而要完成这样的排序,可以用简单暴力的\(~multiset~\),也可以用常数小点的链表。对于第四问,题解太神了。

1416390-20180822082305093-216285674.png

code

#include<bits/stdc++.h>
#define Set(a, b) memset(a, b, sizeof (a))
#define For(i, j, k) for(int i = j; i <= k; ++i)
#define Forr(i, j, k) for(int i = j; i >= k; --i)
using namespace std;

inline int read() {
    int x = 0, p = 1; char c = getchar();
    for(; !isdigit(c); c = getchar()) if(c == '-') p = -1;
    for(; isdigit(c); c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    return x *= p;
}

template<typename T> inline bool chkmin(T &a, T b) { return a > b ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return a < b ? a = b, 1 : 0; }

inline void File() {
    freopen("b.in", "r", stdin);
    freopen("b.out", "w", stdout);
}

const int N = 5e3 + 10, inf = 0x3f3f3f3f;
int n, V, a[N], minn;

namespace Task1 {
    int dp[N];

    inline void Solve() {
        Set(dp, 127), dp[0] = 0;
        For(i, 1, n) Forr(j, V, a[i]) chkmin(dp[j], dp[j - a[i]] + 1);
        minn = dp[V], printf("%.7lf ", 1.0 * V / minn);
    }
}

namespace Task2 {
    
    int dp1[N][N], dp2[N][N];

    inline void Solve() {
        Set(dp1, 127), Set(dp2, 127); dp1[0][0] = 0, dp2[n + 1][0] = 0; 
    
        For(i, 1, n) Forr(j, V, 0) {
            if (j >= a[i]) chkmin(dp1[i][j], dp1[i - 1][j - a[i]] + 1);
            chkmin(dp1[i][j], dp1[i - 1][j]);
        }

        Forr(i, n, 1) Forr(j, V, 0) {
            if (j >= a[i]) chkmin(dp2[i][j], dp2[i + 1][j - a[i]] + 1);
            chkmin(dp2[i][j], dp2[i + 1][j]);
        }

        int res = minn + 1 >> 1;
        For(i, 1, n) For(j, 0, V) {
            if (dp1[i][j] + dp2[i + 1][V - j] == minn && dp1[i][j] == res) {
                printf("%d ", a[i]); return;
            }
        }
    }
}

namespace Task3 {
    int dp[N][N], tmp[N], cnt[N], pre[N], nex[N], A[N];

    inline void Solve() {
        For(i, 1, n) ++ cnt[a[i]], A[i] = a[i];
    
        int tot = unique(A + 1, A + 1 + n) - A - 1;
        For(i, 1, tot) {    
            pre[A[i]] = i == 1 ? A[tot] : A[i - 1];
            nex[A[i]] = i == tot ? A[1] : A[i + 1];
        }
        int c = 0, now = A[1];
        for (;;) {
            if (c == n) break; 
            tmp[++ c] = now, -- cnt[now];
            if (!cnt[now]) nex[pre[now]] = nex[now], pre[nex[now]] = pre[now];
            now = nex[now];
        }   

        Set(dp, 127), dp[0][0] = 0;
        For(i, 1, n) Forr(j, V, 0) {
            if (j >= tmp[i]) chkmin(dp[i][j], dp[i - 1][j - tmp[i]] + 1);
            chkmin(dp[i][j], dp[i - 1][j]);
        }

        For(i, 1, n) {
            ++ cnt[tmp[i]];
            if (dp[i][V] == minn) { printf("%d ", cnt[tmp[i]]); return; }
        }
    }
}

namespace Task4 {

    int dp1[N][N], dp2[N][N], S1[N], S2[N];
    int c1, c2, ans = inf, l = 1, r = 0;

    inline bool check() {
        For(i, 0, V) if (dp1[c1][i] + dp2[c2][V - i] == minn) return true;
        return false;
    }

    inline void clear1() { Set(dp1, 127), dp1[0][0] = 0; }
    inline void clear2() { Set(dp2, 127), dp2[0][0] = 0; }

    inline void Solve() {
        clear1(), clear2();
        while (l <= n && r <= n) {
            if (check()) {
                chkmin(ans, a[r] - a[l]), ++ l;         
                if (!c1) {
                    clear1();
                    Forr(i, c2, 1) {
                        S1[++ c1] = S2[i];
                        For(st, 0, V) dp1[c1][st] = dp1[c1 - 1][st];
                        Forr(j, V, S1[c1]) chkmin(dp1[c1][j], dp1[c1 - 1][j - S1[c1]] + 1);
                    }
                    c2 = 0, clear2();
                } -- c1;        
            } else {
                ++ r, S2[++ c2] = a[r];
                For(st, 0, V) dp2[c2][st] = dp2[c2 - 1][st];
                Forr(j, V, a[r]) chkmin(dp2[c2][j], dp2[c2 - 1][j - a[r]] + 1);
            }
        }

        printf("%d\n", ans);
    }
}

int main() {
    File();
    n = read(), V = read();
    For(i, 1, n) a[i] = read();
    sort(a + 1, a + 1 + n);

    Task1::Solve();
    Task2::Solve();
    Task3::Solve();
    Task4::Solve();
    return 0;   
}

第三题

​ 给你一张\(~n~\)个点\(~m~\)条边的带权无向图,对于一条合法路径,你可以不用支付最贵的\(~k~\)条路,求最短路。\(~1 \leq n, m \leq 3000, ~0 \leq k \leq m, z \leq 10 ^ 9~\)。考虑枚举每一条边的权值\(~w_i~\),使所有边变成\(~max \{0, ~T_i - w_i\}~\), 跑最短路,最后的答案加上\(~k \times T_i~\),取最小值。至于这样为什么是对的,脑补一下一些情况就可以知道了。这个方法很套路,考场上根本想不到。。。

code

#include<bits/stdc++.h>
#define fir first
#define sec second
#define mp make_pair
#define For(i, j, k) for(int i = j; i <= k; ++i)
#define Forr(i, j, k) for(int i = j; i >= k; --i)
#define Travel(i, u) for(int i = beg[u], v = to[i]; i; i = nex[i], v = to[i])
using namespace std;

inline int read() {
    int x = 0, p = 1; char c = getchar();
    for(; !isdigit(c); c = getchar()) if(c == '-') p = -1;
    for(; isdigit(c); c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    return x *= p;
}

inline void File() {
    freopen("y.in", "r", stdin);
    freopen("y.out", "w", stdout);
}

typedef long long ll;
typedef pair<ll, int> PII;
const int N = 3e3 + 10, M = N << 1;
const ll inf = 1e15;
int e = 1, beg[N], nex[M], to[M], vis[N], n, m, k, tot;
ll dis[N], w[M], ans, T[M], ls[M];

inline void add(int x, int y, ll z) {
    to[++ e] = y, nex[e] = beg[x], beg[x] = e, T[e] = z;
    to[++ e] = x, nex[e] = beg[y], beg[y] = e, T[e] = z;
}

inline ll dij(ll x) {
    priority_queue<PII, vector<PII>, greater<PII> > Q;
    For(i, 2, e) w[i] = max(0ll, T[i] - x);
    For(i, 1, n) vis[i] = 0, dis[i] = inf;
    dis[1] = 0, Q.push(mp(0, 1));

    while (!Q.empty()) {
        PII x = Q.top(); int u = x.sec; Q.pop();
        if (!vis[u]) {
            vis[u] = 1;
            Travel(i, u) if(dis[v] > x.fir + w[i]) {
                dis[v] = x.fir + w[i];
                Q.push(mp(dis[v], v));
            }
        }
    }
    return dis[n] + k * x;
}

int main() {
    File();
    cin >> n >> m >> k;
    For(i, 1, m) {
        int x = read(), y = read(), z = read();
        add(x, y, 1ll * z), ls[++ tot] = z;
    }   
    sort(ls + 1, ls + 1 + tot), tot = unique(ls + 1, ls + 1 + tot) - ls - 1;

    ll ans = dij(0);
    For(i, 1, tot) ans = min(ans, dij(ls[i]));
    printf("%lld\n", ans);
    return 0;
}

​ 至于今天的题目名字,连起来就是一个很美丽的名字了。

转载于:https://www.cnblogs.com/LSTete/p/9515575.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值