2019中国大学生程序设计竞赛(CCPC) - 网络选拔赛

我只写了02 03 04,05 gcd 是我离现场切gcd最接近ac的一场,果然数论还是写不出gcd
1002 array

解法:如果这题不带修改操作,相信大家都会做,我就来讲讲怎么处理修改,对于每次修改操作,我不需要真的去修改它,而是把修改的那个元素插入set中去,然后每次查询,我们不带修改查一个答案v1,然后再去set里找第一个大于等于k的v2,min(v1, v2)就是答案。证明:如果v2 >= v1,不影响答案,如果v2<v1,,假设v2在数组中的位置 > r,那么显然很显然前 r 个数中一定不存在v2,那么不带修改查的v1一定小于等于v2,显然矛盾,也就是v2的位置一定<=r,证毕
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5 + 10, N = 1e5 + 1, inf = 1e9;
int rt[maxn], ls[maxn * 24], rs[maxn * 24], mn[maxn * 24], a[maxn], cnt, cat;
void build(int &o, int l, int r) {
    o = ++cat;
    if (l == r) {
        mn[o] = l;
        return;
    }
    int m =(l + r) / 2;
    build(ls[o], l, m);
    build(rs[o], m + 1, r);
    mn[o] = min(mn[ls[o]], mn[rs[o]]);
}
void up(int &o, int pre, int l, int r, int k) {
    o = ++cnt;
    ls[o] = ls[pre];
    rs[o] = rs[pre];
    if (l == r) {
        mn[o] = inf;
        return;
    }
    int m = (l + r) / 2;
    if (k <= m)
        up(ls[o], ls[pre], l, m, k);
    else
        up(rs[o], rs[pre], m + 1, r, k);
    mn[o] = min(mn[ls[o]], mn[rs[o]]);
}
int qu(int o, int l, int r, int ql, int qr) {
    if (l >= ql && r <= qr)
        return mn[o];
    int res = 1e9, m = (l + r) / 2;
    if (ql <= m)
        res = min(res, qu(ls[o], l, m, ql, qr));
    if (qr > m)
        res = min(res, qu(rs[o], m + 1, r, ql, qr));
    return res;
}
set<int> s;
int main()
{
    build(rt[0], 1, N);
    int T;
    scanf("%d", &T);
    while (T--) {
        cnt = cat;
        s.clear();
        int n, q, opt, r, k, ans = 0, x;
        scanf("%d%d", &n, &q);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            up(rt[i], rt[i - 1], 1, N, a[i]);
        }
        while (q--) {
            scanf("%d%d", &opt, &r);
            r ^= ans;
            if (opt == 1)
                s.insert(a[r]);
            else {
                scanf("%d", &k);
                k ^= ans;
                int v = 1e9;
                auto it = s.lower_bound(k);
                if (it != s.end())
                    v = *it;
                int v2 = qu(rt[r], 1, N, k, N);
                ans = min(v, v2);
                printf("%d\n", ans);
            }
        }
    }
}

1003 K-th occurrence

解法:我们先建后缀数组,根据sa数组建可持久化线段树,然后rmq预处理height数组,每次查询,我们根据height二分找到一个最左的合法的左端点LL,和最右的右端点RR,然后在可持久化线段树上找[LL, RR]的第 k 大的位置就行。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+1000, N = 1e5 + 10;
int rt[maxn], ls[maxn * 20], rs[maxn * 20], sum[maxn * 20], cnt;
void up(int &o, int pre, int l, int r, int k) {
    o = ++cnt;
    sum[o] = sum[pre] + 1;
    ls[o] = ls[pre];
    rs[o] = rs[pre];
    if (l == r)
        return;
    int m = (l + r) / 2;
    if (k <= m)
        up(ls[o], ls[pre], l, m, k);
    else
        up(rs[o], rs[pre], m + 1, r, k);
}
int qu(int o, int pre, int l, int r, int k) {
    if (l == r)
        return l;
    int cmp = sum[ls[o]] - sum[ls[pre]], m = (l + r) / 2;
    if (cmp >= k)
        return qu(ls[o], ls[pre], l, m, k);
    return qu(rs[o], rs[pre], m + 1, r, k - cmp);
}
struct S_array
{
    int s[maxn],sa[maxn],t[maxn],t2[maxn],c[maxn],n;
    int mn[maxn][20];
    void build_sa(int m)
    {
        int i,*x=t,*y=t2;
        for(i=0;i<m;i++)c[i]=0;
        for(i=0;i<n;i++)c[x[i]=s[i]]++;
        for(i=1;i<m;i++)c[i]+=c[i-1];
        for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
        for(int k=1;k<=n;k<<=1){
            int p=0;
            for(i=n-k;i<n;i++)y[p++]=i;
            for(i=0;i<n;i++)if(sa[i]>=k)y[p++]=sa[i]-k;
            for(i=0;i<m;i++)c[i]=0;
            for(i=0;i<n;i++)c[x[y[i]]]++;
            for(i=0;i<m;i++)c[i]+=c[i-1];
            for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
            swap(x,y);
            p=1;x[sa[0]]=0;
            for(i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
            if(p>=n)break;
            m=p;
        }
    }
    int rank[maxn],height[maxn];
    void getHeight()
    {
        int i,j,k=0;
        for(i=0;i<n;i++)rank[sa[i]]=i;
        for(i=0;i<n;i++){
            if(k)k--;
            int j=sa[rank[i]-1];
            while(s[i+k]==s[j+k])k++;
            height[rank[i]]=k;
        }
    }
    void init() {
        cnt = 0;
        for (int i = 1; i < n; i++)
            up(rt[i], rt[i - 1], 0, N, sa[i]);
        for (int i = 1; i < n; i++)
            mn[i][0] = height[i];
        for (int i = 1; i < 20; i++)
            for (int j = 1; j - 1 + (1 << i) < n; j++)
                mn[j][i] = min(mn[j][i - 1], mn[j + (1 << (i - 1))][i - 1]);
    }
    int rmq(int l, int r) {
        int k = log2(r - l + 1);
        return min(mn[l][k], mn[r + 1 - (1 << k)][k]);
    }
    int gao(int l, int r, int k) {
        int pos = rank[l - 1];
        int L = 1, R = pos, LL = pos, RR = pos;
        while (L < R) {
            int mid = (L + R) / 2;
            if (rmq(mid, pos) >= r - l + 1)
                R = mid;
            else
                L = mid + 1;
        }
        if (rmq(L, pos) >= r - l + 1)
            LL = L - 1;
        if (pos < n - 1) {
            pos++;
            L = pos, R = n - 1;
            while (L < R) {
                int mid = (L + R) / 2;
                if (rmq(pos, mid) >= r - l + 1)
                    L = mid + 1;
                else
                    R = mid;
            }
            if (rmq(pos, L) < r - l + 1)
                L--;
            if (L >= pos && rmq(pos, L) >= r - l + 1)
                RR = L;
        }
        if (RR - LL + 1 < k)
            return -1;
        return qu(rt[RR], rt[LL - 1], 0, N, k) + 1;
    }
} sa;
char s[maxn];
int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        int n, q, l, r, k;
        scanf("%d%d%s", &n, &q, s + 1);
        for (int i = 1; i <= n; i++)
            sa.s[i - 1] = s[i] - 'a' + 1;
        sa.s[n] = 0;
        sa.n = n + 1;
        sa.build_sa(27);
        sa.getHeight();
        sa.init();
        while (q--) {
            scanf("%d%d%d", &l, &r, &k);
            printf("%d\n", sa.gao(l, r, k));
        }
    }
}

1004 path

解法:我们对于所有点u,对它的出边按照边权排序,然后用一个set维护当前优先队列存了的所有路径长度,但是只维护max(k)个元素,我们把所有边存进优先队列,每次出队,我们枚举当前节点的所有出边,如果set元素数量低于max(k)或者当前路径长度+当前出边小于set最大的元素,那么我们就把新的路径长度更新到set并且存进优先队列,否则中断枚举,直到优先队列出队max(k)次即可
#include<bits/stdc++.h>
#define pi pair<int, int>
#define mk make_pair
#define ll long long
using namespace std;
const int maxn = 5e4 + 10;
struct node {
    int u;
    ll dis;
    bool operator<(const node& t) const {
        return dis > t.dis;
    }
};
vector<pi> G[maxn];
priority_queue<node> q;
multiset<ll> s;
ll ans[maxn * 10];
int  qry[maxn];
void init(int n) {
    s.clear();
    for (int i = 1; i <= n; i++)
        G[i].clear();
    while (!q.empty())
        q.pop();
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        int n, m, Q, u, v, w, k, mx = 0;
        scanf("%d%d%d", &n, &m, &Q);
        for (int i = 1; i <= m; i++) {
            scanf("%d%d%d", &u, &v, &w);
            G[u].push_back(mk(w, v));
            q.push(node{v, w});
            s.insert(w);
        }
        for (int i = 1; i <= n; i++)
            sort(G[i].begin(), G[i].end());
        for (int i = 1; i <= Q; i++)
            scanf("%d", &qry[i]), mx = max(mx, qry[i]);
        int cnt = 0;
        while (cnt < mx) {
            node tmp = q.top();
            q.pop();
            ans[++cnt] = tmp.dis;
            if (cnt >= mx)
                break;
            u = tmp.u;
            for (auto it : G[u]) {
                v = it.second;
                ll w = it.first + tmp.dis;
                if (s.size() == mx) {
                    auto cat = --s.end();
                    if (w >= *cat)
                        break;
                    s.erase(cat);
                    s.insert(w);
                }
                else
                    s.insert(w);
                q.push(node{v, w});
            }
        }
        for (int i = 1; i <= Q; i++)
            printf("%lld\n", ans[qry[i]]);
        init(n);
    }
}

1005 huntian oy
其实问题可以转化成:
                                      ∑ i = 1 n ∑ j = 1 i ( i − j ) [ g c d ( i , j ) = 1 ] \sum_{i = 1}^{n}\sum_{j = 1}^{i}(i-j)[gcd(i,j)=1] i=1nj=1i(ij)[gcd(i,j)=1]
进一步转化:
                                    ∑ i = 1 n i φ ( i ) − ∑ i = 1 n ∑ j = 1 i j [ g c d ( i , j ) = 1 ] \sum_{i =1}^{n}i\varphi (i)-\sum_{i=1}^{n}\sum_{j=1}^{i}j[gcd(i,j)=1] i=1niφ(i)i=1nj=1ij[gcd(i,j)=1]
对于所有 i ( i &gt; = 2 ) i(i&gt;=2) i(i>=2),假设 j j j i i i互质,那么 i − j i-j ij一定与 i i i互质,也就是说所有小与 i i i g c d ( i , j ) = 1 gcd(i,j)=1 gcd(i,j)=1 j j j的总和,一定等于 i φ ( i ) 2 \frac{i\varphi (i)}{2} 2iφ(i),那么上式变形:
                                              a n s = 1 2 ∑ i = 2 n i φ ( i ) ans=\frac{1}{2}\sum_{i=2}^{n}i\varphi (i) ans=21i=2niφ(i)
这就是个简单杜教筛,于是搞定这题啦

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 5e6 + 10, N = 5e6, mod = 1e9 + 7;
int pri[maxn], vis[maxn], phi[maxn], cnt, inv, inv2;
unordered_map<int, int> mp;
void add(int &x, int y) {
    x += y;
    if (x >= mod)
        x -= mod;
    if (x < 0)
        x += mod;
}
ll ksm(ll x, int y) {
    ll res = 1;
    while (y) {
        if (y & 1)
            res = res * x % mod;
        x = x * x % mod;
        y /= 2;
    }
    return res;
}
void init(int n) {
    phi[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!vis[i])
            pri[++cnt] = i, phi[i] = i - 1;
        for (int j = 1; j <= cnt && pri[j] * i <= n; j++) {
            vis[pri[j] * i] = 1;
            if (i % pri[j])
                phi[i * pri[j]] = phi[i] * phi[pri[j]];
            else {
                phi[i * pri[j]] = phi[i] * pri[j];
                break;
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        phi[i] = phi[i - 1] + 1ll * phi[i] * i % mod;
        add(phi[i], 0);
    }
}
int calc(int n) {
    return 1ll * n * (n + 1) / 2 % mod;
}
int calc1(int n) {
    return 1ll * n * (n + 1) % mod * (2 * n + 1) % mod * inv2 % mod;
}
int dfs1(int n) {
    if (n <= N)
        return phi[n];
    if (mp.count(n))
        return mp[n];
    int ans = calc1(n);
    for (int l = 2, r; l <= n; l = r + 1) {
        r = n / (n / l);
        ans -= 1ll * (calc(r) - calc(l - 1) + mod) * dfs1(n / l) % mod;
        add(ans, 0);
    }
    mp.emplace(n, ans);
    return ans;
}
int main()
{
    inv = ksm(2, mod - 2);
    inv2 = ksm(6, mod - 2);
    init(N);
    int T;
    scanf("%d", &T);
    while (T--) {
        int n, a, b;
        scanf("%d%d%d", &n, &a, &b);
        int ans = dfs1(n) - 1;
        ans = 1ll * ans * inv % mod;
        printf("%d\n", ans );
    }
}
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长沙橘子猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值