• 2018-2019 ACM-ICPC 徐州区域赛 部分题解


    题目链接:2018-2019 ACM-ICPC, Asia Xuzhou Regional Contest

    A. Rikka with Minimum Spanning Trees

    题意:

    给出一个随机算法生成边的信息,然后求最小生成树的个数以及其权值的乘积。

    题解:

    这个随机算法有点神奇...基本不会有重复的边出现,所以其实只用求MST就行了。当然,其实通过样例也可以猜出来,样例生成了1W条边,但最后的answer就为最小生成树权值,所以可以直接根据这个来猜一发,注意一下判断是否连通就行了。

    代码如下:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int MAXN = 1e5+5;
    const int MAXM = 3e5+5;
    const int MOD = 1e9+7;
    const double eps = 1e-7;
    typedef unsigned long long ull;
    #define rep(i,a,b) for(int i = (a);i<=(b);i++)
    struct Edge{
        int u,v;
        ull w;
        bool operator <(const Edge &ds)const{
            return w<ds.w;
        }
    }e[MAXM];
    int n,m,fa[MAXN];
    int t,tot;
    //double G[MAXN][MAXN];
    ull k1,k2;
    ull ans=0;
    ull xorShift128Plus(){
        ull k3=k1,k4=k2;
        k1=k4;
        k3 ^= k3<<23;
        k2 = k3 ^ k4 ^ (k3 >>17) ^(k4 >>26);
        return k2 +k4;
    }
    void gen(){
        cin >>n >>m >>k1 >>k2;
        int u,v;
        ull w;
        tot = ans = 0;
        rep(i,1,m){
            u = xorShift128Plus()%n+1;
            v = xorShift128Plus()%n+1;
            w = xorShift128Plus();
            if(u == v) continue ;
            //cout <<u<<' ' <<v<<' ' <<w <<'
    ';
            e[++tot] = {u,v,w};
        }
    }
    int find(int x){
        return x==fa[x]?x:fa[x] = find(fa[x]);
    }
    bool kruskal(){
        int ss=0;
        sort(e+1,e+1+tot);
        rep(i,1,n)fa[i] =i;
        rep(i,1,tot){
            int a = find(e[i].u),b =find(e[i].v);
            if(a!=b){
                fa[a] =b;
                ans = (ans +e[i].w%MOD)%MOD;
                ss++;
            }
        }
        return ss==n-1;
    }
    int main() {
        ios::sync_with_stdio(false);cin.tie(0) ;
        cin >>t;
        while(t--){
            gen();
            if(!kruskal()){
                cout << 0<<'
    ';
            }else{
                cout << ans <<'
    ';
            }
        }
        return 0 ;
    }
    View Code

    G. Rikka with Intersections of Paths

    题意:

    树上给出若干条简单路径,问有多少选k条路径的方案,满足这些路径的交至少有一个点。

    题解:

    考虑求出LCA,因为树上简单路径至少存在一个交点为至少一条路径的两端点的LCA,同时可以利用树上差分求出有多少条路径经过当前点。

    之后计算贡献就行了,但是这里直接计算C(cnt, k)会有重复计算的,我们这里可以考虑刚才关于LCA的性质,求出每个点为多少条路径端点的LCA,个数记为pi,那么最后答案就是C(cnt,k) - C(cnt - pi,k),此时选出的路径中,至少有一条路径的两端点的LCA为当前点,此时就不会重复计算了。因为一条路径的LCA只有一个,我们只会计算经过LCA时的贡献。

    代码如下:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 3e5 + 5, MOD = 1e9 + 7;
    int T, n, m, k;
    struct Edge{
        int u,v,next;
    }e[N << 1];
    int head[N],tot;
    void adde(int u, int v) {
        e[tot].v = v;
        e[tot].next = head[u] ;
        head[u] = tot++ ;
    }
    int deep[N], st[N][22], sum[N], cnt[N];
    ll fac[N], inv[N];
    void dfs(int u, int d, int fa) {
        st[u][0] = fa;
        deep[u] = d;
        for(int i = head[u]; i != -1; i = e[i].next) {
            int v = e[i].v;
            if(v == fa) continue ;
            dfs(v, d + 1, u);
        }
    }
    ll qp(ll a, ll b) {
        ll ans = 1;
        while(b) {
            if(b & 1) ans = ans * a % MOD;
            a = a * a % MOD;
            b >>= 1;
        }
        return ans ;
    }
    void init() {
        for(int i = 1 ; i <= 20 ; i++)
            for(int j = 1 ; j <= n ; j++)
                st[j][i] = st[st[j][i - 1]][i - 1];
    }
    int LCA(int x, int y) {
        if(deep[y] > deep[x]) swap(x , y) ;
        while(deep[x] != deep[y]) {
            int d = deep[x] - deep[y] ;
            for(int i = 0 ; i <= 20 ; i++) {
                if(d >> i & 1) x = st[x][i] ;
            }
        }
        if(x == y) return x ;
        for(int i = 20 ; i >= 0; i--) {
            if(st[x][i] != st[y][i]) {
                x = st[x][i] ;
                y = st[y][i] ;
            }
        }
        return st[x][0] ;
    }
    void pre(int u, int fa) {
        for(int i = head[u] ; i != -1; i = e[i].next) {
            int v = e[i].v;
            if(v == fa) continue ;
            pre(v , u);
            sum[u] += sum[v] ;
        }
    }
    ll C(ll a ,ll b) {
        if(b == 0 || a < b) return 0 ;
        return fac[a] * inv[b] % MOD * inv[a - b] % MOD;
    }
    ll calc(ll x) {
        ll ans = C(sum[x], k);
        ans = ((ans - C(sum[x] - cnt[x], k) % MOD ) % MOD + MOD ) % MOD;
        return ans ;
    }
    int main() {
        ios::sync_with_stdio(false);cin.tie(0);
        fac[0] = 1;
        inv[0] = 1;
        for(int i = 1; i < N; i++) {
            fac[i] = fac[i - 1] * i % MOD;
            inv[i] = qp(fac[i] , MOD - 2) ;
        }
        cin >> T;
        while(T--) {
            memset(head,-1,sizeof(head)); tot = 0;
            cin >> n >> m >> k ;
            for(int i = 1 ; i < n ; i++) {
                int u, v;
                cin >> u >> v;
                adde(u, v);adde(v, u);
            }
            dfs(1, 0, 0);
            init() ;
            for(int i = 1; i <= m ;i++) {
                int u, v;
                cin >> u >> v ;
                int x = LCA(u , v);
                sum[u]++;sum[v]++;
                sum[x]--;sum[st[x][0]]--;
                cnt[x]++;
            }
            pre(1, 0) ;
            ll ans = 0;
            for(int i = 1; i <= n; i++) {
                ans = (ans + calc(i)) % MOD ;
            }
            cout << ans << '
    ' ;
            for(int i = 1; i <= n ; i++) deep[i] = sum[i] = cnt[i] = 0;
        }
        return 0 ;
    }
    View Code

    H. Rikka with A Long Colour Palette

    题意:

    给出n个区间,k个颜色,现在给区间染色,问怎么染色能使得覆盖有所有颜色的区间长度最大。

    题解:

    染色过程考虑贪心。我们先按照区间左端点排序,然后依次涂颜色,颜色涂完了之后该怎么涂呢?假设当前左端点为L,并且在之前的区间中,有Ri,Rj,Rk满足Ri < L  < Rj < Rk,那么此时我们涂Ri的颜色肯定是最优的;如果不存在一个Ri,满足L < Ri,这时我们也只需要涂处于最左端的Ri,因为这样可以尽可能地增多所有颜色覆盖的区间长度。

    最后就考虑如果计算答案了,将所有点排序后记录左端点为1,右端点为-1,累计前缀和,当和大于等于k时更新答案即可。

    代码如下:

    #include <bits/stdc++.h>
    #define mp make_pair
    using namespace std;
    typedef long long ll;
    typedef pair<int,int> pii;
    const int N = 2e5 + 5;
    int T;
    int n, k, cnt;
    pair<pii,int> a[N << 1];
    int col[N], has[N], answer[N];
    struct Node {
        int l, r, id ;
        bool operator < (const Node &A) const {
            if(l == A.l) return r < A.r;
            return l < A.l;
        }
    }p[N];
    int main() {
        ios::sync_with_stdio(false);cin.tie(0);
        cin >> T;
        while (T--) {
            cin >> n >> k;
            cnt = 0;
            for(int i = 1 ; i <= n ; i++) {
                int l, r;
                cin >> l >> r;
                p[i] = Node{l,r,i};
            }
            if(n < k) {
                cout << 0 << '
    ' ;
                for(int i = 1; i < n; i++)
                    cout << 1 << ' ' ;
                cout << 1 << '
    ' ;
                continue ;
            }
            sort(p + 1 , p + n + 1);
            priority_queue <pii> q;
            for(int i = 1 ; i <= k ; i++) {
                q.push(mp(0, i)) ;
            }
            for(int i = 1 ; i <= n ; i++) {
                int now = q.top().second;q.pop();
                answer[p[i].id] = col[i] = now;
                q.push(mp(-p[i].r,now)) ;
            }
            for(int i = 1; i <= n ;i++) {
                a[++cnt] = mp(mp(p[i].l,col[i]),1) ;
                a[++cnt] = mp(mp(p[i].r,col[i]),-1) ;
            }
            sort(a + 1, a + cnt + 1) ;
            int ans = 0, cur = 0;
            for(int i = 1 ; i <= cnt ; i++) {
                if(has[a[i].first.second]) cur--;
                has[a[i].first.second] += a[i].second ;
                if(has[a[i].first.second]) cur++;
                if(cur >= k )
                    ans += a[i + 1].first.first - a[i].first.first;
            }
            cout << ans << '
    ' ;
            for(int i = 1; i < n ; i++) cout << answer[i] << ' ';
            cout << answer[n] << '
    ' ;
            for(int i = 1; i <= k ; i++) has[i] = 0;
        }
        return 0;
    }
    View Code

    I. Rikka with Sorting Networks

    题意:

    给出n个数,有k个排序器,每个排序器会使得au < av,即让他们的位置相对有序,问有多少个排列,最后通过这k个排序器后,形成的序列最长上升子序列至少为n - 1。

    题解:

    满足条件的最长上升子序列个数为(n - 1) ^ 2 + 1个,由于数据范围很小,我们直接构造出来爆搜求解即可。

    代码如下:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 55;
    int a[N], b[N] ;
    int n, k, T, mod;
    int from[N], to[N] ;
    int ans;
    void dfs(int i, int f) {
        if(i == 0) {
            ans += f;
            if(ans >= mod) ans -= mod;
            return ;
        }
        if(a[from[i]] < a[to[i]]) {
            dfs(i - 1, f) ;
            swap(a[from[i]], a[to[i]]) ;
            dfs(i - 1, f) ;
            swap(a[from[i]], a[to[i]]) ;
        }
    }
    int main() {
        ios::sync_with_stdio(false);cin.tie(0);
        cin >> T;
        while(T--) {
            cin >> n >> k >> mod;
            for(int i = 1 ; i <= k ; ++i) cin >> from[i] >> to[i] ;
            for(int i = 1 ; i <= n ; ++i) a[i] = i ;
            ans = 0;
            dfs(k, 1) ;
            for(int i = 1 ; i < n ; ++i) {
                swap(a[i], a[i + 1]);
                dfs(k, -1);
                swap(a[i], a[i + 1]);
            }
            for(int take = 1 ; take <= n ; ++take) {
                for(int i = 1 ; i <= n ; ++i) {
                    if(i == take) continue ;
                    int cur = 0;
                    for(int j = 1 ; j <= n ; ++j) {
                        if(cur == take - 1) cur++;
                        if(i == j) a[j] = take ;
                        else a[j] = ++cur;
                    }
                    dfs(k , 1);
                }
            }
            cout << ans << '
    ';
        }
    
        return 0;
    }
    View Code
  • 相关阅读:
    【NOIp复习】图论算法模板合集
    【NOI导刊】【归并排序求逆序对】最接近神的人
    【NOIp 2012】【线段树】借教室
    【NOIp复习】最近公共祖先LCA&区间最大最小RMQ
    【NOIp 2015】【二分答案】跳石头
    【NOIp 2015】【DFS】斗地主
    【vijos】【BFS+hash】毒药?解药?
    【NOIp模拟】【dp】俄罗斯方块
    【NOIp模拟】【二分图or并查集】GoToandPlay
    【vjios】【DFS】切蛋糕
  • 原文地址:https://www.cnblogs.com/heyuhhh/p/10679868.html
Copyright © 2020-2023  润新知