第十四届华中科技大学程序设计竞赛决赛同步赛(部分)

能做的都没做啊。。。。。 跟着榜走。。。。。 心情复杂。。

A - Beauty of Trees

题意:
题意:
             n n 个数,每次给你一个信息l,r,k,代表 al xor al+1... xor ar=k a l   x o r   a l + 1 . . .   x o r   a r = k ,问你哪些信息是错误的,如果 x x 信息和y信息只可以 x x y错或者 x x y对,那么认为先给出的信息是对的。

思路:
并查集路径压缩。 记 a a 数组的前缀异或和是sum,那么信息 l,r,k l , r , k 实际上就是 sumr xor suml1=k s u m r   x o r   s u m l − 1 = k ,如果已知 sumr xor sumx=k1,suml1 xor sumx=k2 s u m r   x o r   s u m x = k 1 , s u m l − 1   x o r   s u m x = k 2 ,那么只要判断 k1 xor k2 k 1   x o r   k 2 是否等于 k k 即可,否则设sumr xor sumx=k1,suml1 xor sumy=k2,那么有 sumx xor sumy=k1 xor k2 xor k s u m x   x o r   s u m y = k 1   x o r   k 2   x o r   k , 可以合并 x,y x , y , 并设 sum[x] xor sum[y]=k1 xor k2 xor k s u m [ x ]   x o r   s u m [ y ] = k 1   x o r   k 2   x o r   k , 然后直接用路径压缩就好了。

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

int n, m, T, kase = 1;
int pre[maxn], sum[maxn];

int findset(int x) {
    if(x == pre[x]) return x;
    int px = pre[x];
    pre[x] = findset(pre[x]);
    sum[x] ^= sum[px];
    return pre[x];
}

int main() {
    while(scanf("%d %d", &n, &m) != EOF) {
        vector<int> vec;
        for(int i = 0; i <= n; i++) {
            sum[i] = 0;
            pre[i] = i;
        }
        for(int i = 1; i <= m; i++) {
            int l, r, k;
            scanf("%d %d %d", &l, &r, &k);
            l--;
            int fl = findset(l);
            int fr = findset(r);
            if(fl == fr) {
                if((sum[l] ^ sum[r]) != k) vec.push_back(i);
            } else {
                pre[fl] = fr;
                sum[fl] = sum[l] ^ sum[r] ^ k;
            }
        }
        if(!vec.size()) vec.push_back(-1);
        for(int i = 0; i < vec.size(); i++) printf("%d\n", vec[i]);
    }
    return 0;
}


D - Mr. Qiang and His Motorcycles

题意:
             给你一棵 n(3000) n ( ⩽ 3000 ) 个节点每个节点带权值的树, q(q10) q ( q ⩽ 10 ) 个查询,每次给你一个 w w ,问这棵树中有多少个连通块权值和刚好是w

思路:
             先跑出 dfs d f s 树,设: dp[x][w] d p [ x ] [ w ] : x x 为根且经过x节点大小为 w w 的连通块的数量,一次考虑每个孩子,处理完之后合并到父节点上去,当考虑到v孩子节点的时候,以 x x 为根大小为d的连通块总数 Sd S d 为:

Sd=i=0d(dp[x][i]dp[v][Si]) S d = ∑ i = 0 d ( d p [ x ] [ i ] ∗ d p [ v ] [ S − i ] )

             这里求快速卷积, 给了取模的数是 998244353 998244353 ,直接快速 NTT N T T 求解。

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

typedef pair<int, int> pr;
int del[maxn], sz[maxn], wt[maxn];
vector<int> G[maxn];
ll dp[3010][2010], rec[maxn * 20];
int n, m, k;
ll rev[maxn * 20], a[maxn * 20], b[maxn * 20];

ll qmod(ll x, ll n, ll mod) {
    ll ans = 1;
    while(n) {
        if(n & 1) ans = ans * x % mod;
        x = x * x % mod;
        n >>= 1;
    }
    return ans;
}

void NTT(ll *a, ll len, int op) {
    for(int i = 0; i < len; i++) if(i < rev[i]) swap(a[i], a[rev[i]]);
    for(int i = 1; i < len; i <<= 1) {
        ll wn = qmod(3, ((mod - 1) / i / 2 * op + mod - 1) % (mod - 1), mod);
        int step = i << 1;
        for(int j = 0; j < len; j += step) {
            ll w = 1, x, y;
            for(int k = 0; k < i; k++, w = (w * wn) % mod) {
                x = a[j + k]; y = (w * a[j + k + i]) % mod;
                a[j + k] = (x + y) % mod; a[j + k + i] = ((x - y) % mod + mod) % mod;
            }
        }
    }
}

void solve_NTT(int x, int y, int wi) {
    int len = 1, l = 0;
    while((len <= 2 * wi)) { len <<= 1; l++; }
    for(int i = 0; i < len; i++) {
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
        if(i <= wi) { a[i] = dp[x][i]; b[i] = dp[y][i]; }
        else a[i] = b[i] = 0;
    }
    NTT(a, len, 1); NTT(b, len, 1);
    for(int i = 0; i < len; i++) a[i] = a[i] * b[i] % mod;
    NTT(a, len, -1);
    ll inv = qmod(len, mod - 2, mod);
    for(int i = 0; i <= wi; i++) rec[i] = a[i] * inv % mod;
}

ll ans;
void DFS(int x, int fa, int wi) {
    for(int i = 0; i <= wi; i++) dp[x][i] = 0;
    dp[x][wt[x]] = 1;
    for(int i = 0; i < G[x].size(); i++) {
        int to = G[x][i];
        if(to == fa || del[to]) continue;
        DFS(to, x, wi);
        solve_NTT(x, to, wi);
        for(int j = 0; j <= wi; j++) dp[x][j] = (dp[x][j] + rec[j]) % mod;
    }
    ans += dp[x][wi];
}


int main() {
    while(scanf("%d", &n) != EOF) {
        for(int i = 1; i <= n; i++) {
            scanf("%d", &wt[i]);
            G[i].clear();
        }
        for(int i = 1; i < n; i++) {
            int u, v;
            scanf("%d %d", &u, &v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        scanf("%d", &m);
        DFS(1, 0, 2000);
        while(m--) {
            ll wi; scanf("%lld", &wi);
            ans = 0;
            for(int i = 1; i <= n; i++) ans += dp[i][wi];
            printf("%lld\n", ans % mod);
        }
    }
    return 0;
}


E - A Simple Problem

题意:
             一个 n n 个数的数组a(每个数的值 <11 < 11 <script type="math/tex" id="MathJax-Element-220"><11</script>),然后给一个 m m 个数的数组b b b 数组中的数可以任意换成其他数,但是不能几个不同的数换成同一个数,同一个数也只能唯一换成另一个数,就是说如果第一个1换成了 2 2 ,那么后面所有的1都要换成 2 2 ,问a数组中能有多少个可以和 b b 匹配的子串。

思路:
    第二个数组b可以从右往左考虑,每一个不同的数出现的位置,比如样例 1 2 1 2 1   2   1   2 ,那么第 0 0 个数第一次出现在3这个位置(序列中的 2 2 ),第0种数有这些位置出现了:
             0 0 3 1 1
同样第1种数就是:
             1 1 2 0 0
     0 0 1的下标位置哈希(求和比较方便),然后随便乱搞几下记录 b b 串的哈希。
    那么对于 a a 数组,从左往右考虑,考虑到ai的时候,那么对于的判断区间 [l,r] [ l , r ] 就是 [im+1,i] [ i − m + 1 , i ] ,既然所有不同的数记录下来了,那么可以知道, a[l+0], a [ l + 0 ] , a[l+2] a [ l + 2 ] 是第1种数, a[l+1] a [ l + 1 ] a[l+3] a [ l + 3 ] 是第 0 0 种数,可以简单认为a[l+0]是第 1 1 种数,a[l+1]是第 0 0 种数,那么求出a[l+0]在区间 [l,r] [ l , r ] 的下标相对于 l l 的位置的和(也就是第0种数的哈希值, 所以求和方便),这里求和可以直接前缀,假设在区间 [l,r] [ l , r ] 中所有是 a[l+0] a [ l + 0 ] 的下标是是 i1,i2,..,ik i 1 , i 2 , . . , i k , 那么相对位置的哈希值就是 i1l+i2l+...+ikl=(i1+i2+...+ik)kl i 1 − l + i 2 − l + . . . + i k − l = ( i 1 + i 2 + . . . + i k ) − k ∗ l (第 1 1 种数的个数 l也行, 只是求哈希, 如果最终结果相同的话肯定 k= k = 1 1 种数的个数 ),这样就是对于新的第1种数的下标的哈希值, 后面的照样处理,最后判断哈希值是否和 b b 串的哈希值相同即可。

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

int n, m, a[maxn], b[maxn], k;
int use[13];
vector<ll> ag[20], sa[20], ha[20];
int tot[maxn], mp[20], show[20];
ll hh[20];

int main() {
    while(scanf("%d %d", &n, &k) != EOF) {
        for(int i = 0; i < 20; i++) {
            ag[i].clear();
            ha[i].clear();
            sa[i].clear();
        }
        set<int> st;
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            ag[a[i]].push_back(i);
            sa[a[i]].push_back(i);
            int sz = sa[a[i]].size();
            if(sz <= 1) continue;
            sa[a[i]][sz - 1] += sa[a[i]][sz - 2];
        }
        scanf("%d", &m);
        for(int i = 0; i < m; i++) scanf("%d", &b[i]);
        int flag = 0;
        for(int i = m - 1; i >= 0; i--) {
            int t = b[i];
            if(!st.count(t)) mp[t] = flag++;
            st.insert(t); ha[mp[t]].push_back(i + 1);
        }
        ll val = 0, c = 1;
        for(int i = 0; i < flag; i++) {
            ll su = 0; c = c * 3;
            for(int j = 0; j < ha[i].size(); j++) {
                su += ha[i][j];
            }
            su = su * c * (i + 1) % mod;
            val = (val + su) % mod;
        }
        int ans = 0;
        for(int i = 1; i <= n; i++) {
            if(i < m) continue;
            memset(use, 0, sizeof use);
            int l = i - m + 1, r = i, cc = 1;
            ll hs = 0;
            for(int j = 0; j < flag; j++) {
                int x = l + ha[j][0] - 1; cc = cc * 3;
                x = a[x];
                if(use[x]) { hs = -mod - 5; break; }
                use[x] = 1;
                int idl = lower_bound(ag[x].begin(), ag[x].end(), l) - ag[x].begin();
                int idr = upper_bound(ag[x].begin(), ag[x].end(), r) - ag[x].begin() - 1;
                ll su = sa[x][idr];
                if(idl) su -= sa[x][idl - 1];
                ll t = ha[j].size();
                su -= t * (l - 1);
                su = su * cc * (j + 1) % mod;
                hs = (hs + su) % mod;
            }
            if(hs == val) ans++;
        }
        printf("%d\n", ans);
    }
    return 0;
}


K - Maximum average Sequence

题意:
    给你 n n 个数组的序列,现在要你选出一个子序列,使得子序列满足整除关系的对数除以子序列长度最大。一对整除关系是指:如果子序列中的ai整除 aj a j 或者 aj a j 整除 ai a i ,那么 (i,j) ( i , j ) 就是一对整除关系。

思路:
             原序列中有整除关系的元素之间连一条边权值为 1 1 ,那么就是选出一个子图使得边的权值和除以顶点数最大, 然后二分答案, 这是裸的最大密度子图。。。。 最大密度子图可以参考胡伯涛的论文《最小割模型在信息学竞赛中的应用》。

#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 2e4 + 10;
const int INF = 1e9 + 10;
const double eps = 1e-6;
using namespace std;

struct st {
    int to, re; double cap;
    st(int t = 0, double 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, double 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 > eps && lv[e.to] < 0) {
                lv[e.to] = lv[u] + 1;
                q.push(e.to);
            }
        }
     }
}

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

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

int from[maxn], to[maxn], vis[maxn];

double build_graph(double mid) {
    s = 0; t = n + m + 1;
    double tot = m;
    for(int i = 0; i <= n + m + 1; i++) G[i].clear();
    for(int i = 1; i <= n; i++) add(i, t, mid);
    for(int i = 1; i <= m; i++) {
        add(s, i + n, 1);
        add(i + n, from[i], INF);
        add(i + n, to[i], INF);
    }
    double res = maxflow();
    tot = tot - res;
    return tot;
}

int a[300];

int main() {
    while(scanf("%d", &n) != EOF) {
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
        m = 1;
        for(int i = 1; i <= n; i++) {
            for(int j = i + 1; j <= n; j++) {
                if(a[i] % a[j] == 0 || a[j] % a[i] == 0) {
                    from[m] = i; to[m++] = j;
                }
            }
        }
        m--;
        double l = 0, r = 1010;
        while(l + 1e-9 < r) {
            double mid = (l + r) / 2;
            double sol = build_graph(mid);
            if(sol > 0) l = mid;
            else r = mid;
        }
        printf("%.8f\n", l);
    }
    return 0;
}


I - Magic Forest

题意:
    给出 n n 个数的数组a还有 m m 个查询,每次给一个X,求一个 k k 数组使得i=1naiki=X

思路:
             基本相当于裸的扩展欧几里得了…求个前缀 gcd g c d ,如果 gcdnX g c d n ∤ X 那么无解,否则我们知道:
GCD(gcdn1,an)=gcdn G C D ( g c d n − 1 , a n ) = g c d n ,可以通过扩展欧几里得求得 gcdn1x+any=gcdn g c d n − 1 ∗ x + a n ∗ y = g c d n ,求出 x x y y y 就是an的系数,然后类似处理 gcdn1 g c d n − 1 ,之后的所有系数都乘以 x x <script type="math/tex" id="MathJax-Element-303">x</script>即可。

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

typedef pair<ll, ll> pa;
ll n, m, T, a[maxn];
ll gg[maxn], ans[maxn];

ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}

void exgcd(ll a, ll b, ll &d, ll &x, ll &y) {
    if(!b) { d = a; x = 1; y = 0; }
    else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}

int main() {
    while(scanf("%lld %lld", &n, &m) != EOF) {
        for(int i = 1; i <= n; i++) {
            scanf("%lld", &a[i]);
            if(i == 1) gg[i] = a[i];
            else gg[i] = gcd(gg[i - 1], a[i]);
        }
        while(m--) {
            ll x; scanf("%lld", &x);
            if(x % gg[n]) { printf("NO\n"); continue; }
            if(n == 1) { printf("%lld\n", x / a[1]); continue; }
            ll aa, bb = a[n], xx, yy, d, t = x / gg[n];
            for(int i = n - 1; i >= 1; i--) {
                aa = gg[i];
                exgcd(aa, bb, d, xx, yy);
                ans[i + 1] = yy * t;
                ans[i] = xx * t;
                bb = a[i]; t *= xx;
            }
            for(int i = 1; i <= n; i++) printf("%lld%c", ans[i], i < n ? ' ' : '\n');
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值