The 7th Guangxi Collegiate Programming Contest(第七届广西大学生程序设计竞赛)题解——ADHJKM

先点这个才可以进去
cf链接

A

期望具有线性性,可以将第一次选取的数为为 i i i 的期望算出来,再把他们相加除以 n n n 就是答案,问题转化为 对于第一次选取的数 i i i 的期望长度怎么算,
对于一个 k k k ,设 f [ i ] f[i] f[i] 是以 i i i 开始的期望长度,我们手模几个样例,发现如果选出来的数 i ≥ k i\geq k ik f [ i ] = 1 f[i]=1 f[i]=1 , 对于 i < k i<k i<k , f [ i ] = 1 + 1 n − i ∑ j = i + 1 n f [ j ] f[i] = 1+\frac{1}{n-i}\sum_{j=i+1}^{n} f[j] f[i]=1+ni1j=i+1nf[j] ,如何理解相当于从 i i i 开始已经长度为 1 了,我只能选择比 i i i 大的数,对于 [ i + 1 , n ] [i+1,n] [i+1,n] 每个数我有 1 n − i \frac{1}{n-i} ni1 的概率选中它。因为期望具有线性性,所以求和相加除以平均数+1就是以 i i i 开始的期望长度,但是这还不足以通过本题,如果每次跑一遍递推就是 n 2 n^2 n2 的复杂度,发现还可以优化,发现 d p dp dp 式子好像是一个后缀和的形式,那么可以维护后缀和 s u m sum sum ,那么每次的递推不就变成了 f [ i ] = 1 + 1 n − i ⋅ s u m f[i] = 1+\frac{1}{n-i}\cdot sum f[i]=1+ni1sum, s u m = s u m + f [ i ] = 1 + n − i + 1 n − i s u m sum = sum+f[i]=1+\frac{n-i+1}{n-i}sum sum=sum+f[i]=1+nini+1sum,我们要求的 f [ 1 ] f[1] f[1] 已经被包含在最后的 s u m sum sum 里面,那么就不需要 f f f 的递推了,只需要维护 s u m sum sum 即可, s u m sum sum 发现是一次函数复合.
f = a x + b , g = c x + d f = ax+b,g = cx+d f=ax+b,g=cx+d, ( f ∘ g ) ( x ) = f ( g ( x ) ) = a ( c x + d ) + b (f\circ g)(x) = f(g(x)) = a(cx+d)+b (fg)(x)=f(g(x))=a(cx+d)+b,写成映射形式就是 ( a , b ) ∘ ( c , d ) ↦ ( a c , a d + b ) (a,b) \circ (c,d)\mapsto (ac,ad+b) (a,b)(c,d)(ac,ad+b) ,那么我们只要根据这个维护关于 s u m sum sum 一次函数的嵌套即可 O ( 1 ) O(1) O(1)求解,需要注意的是维护的嵌套顺序不对,需要求解上面映射的逆映射,这题卡空间,不清楚是否可以通过,不过可以提一嘴逆映射怎么求。
复合类似于于左乘矩阵,求逆也是类似的。具体的,现有一次函数复合 F ( x ) = f 1 ( f 2 ( f 3 ( x ) ) ) F(x)=f_1(f_2(f_3(x))) F(x)=f1(f2(f3(x))) ,现在我想得到 f 1 ( x ) f_1(x) f1(x) ,那么我可以 F ∘ g ( x ) = f 1 ( f 2 ( f 3 ( g ( x ) ) ) ) F\circ g(x)= f_1(f_2(f_3(g(x)))) Fg(x)=f1(f2(f3(g(x)))) , 其中 g ( x ) = f 3 − 1 ( f 2 − 1 ( x ) ) f 3 − 1 是 f 3 的逆映射 g(x) = f_3^{-1} (f_2^{-1}(x))\quad f_3^{-1} 是 f_3的逆映射 g(x)=f31(f21(x))f31f3的逆映射
该题还有一个细节,因为 n n n 很大,需要线性求逆元

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;
constexpr int maxn = 1e7+10;
constexpr int mod = 998244353;
int n,inv[maxn];
array<int,2> pre[maxn];
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);
    cin>>n;
    inv[1] = 1;
    for(int i = 2; i <= n; i++){
        inv[i] = 1LL*(mod-mod/i)*inv[mod % i]%mod;
    }
    pre[1]={1LL*n*inv[n-1]%mod,1};
    for(int i = 2;i<n;++i){
        pre[i][0] = 1LL*pre[i-1][0]*(n-i+1LL)%mod*inv[n-i]%mod;
        pre[i][1] = (1LL*pre[i-1][0]+pre[i-1][1])%mod; 
    }

    i64 ans = n;
    i64 A,B;
    for(int k = n;k>1;--k){
        A = pre[k-1][0];
        B = pre[k-1][1];
        //cout<<k<<" "<<A<<" "<<B<<"\n";
        (ans+=(A*(n-k+1LL)+B))%=mod;
    }
    (ans*=inv[n])%=mod;
    (ans*=inv[n])%=mod;
    cout<<ans<<"\n";
    return 0;
}

D

注意题目描述,时间可以是实数。首先可以观察到关键时间节点就 2 ∗ n 2*n 2n 个,意味着有线段覆盖到 x x x 或者没有交点,那么我们可以对于每个时间节点排序,从上一个时间节点到当前节点的虫子我们可以求出,用 s e t set set 维护虫子长度,对关键节点的 m i n 虫子长度 min虫子长度 min虫子长度 m a x max max 即可

#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;
#define int long long

constexpr int maxn = 1e5+10;
struct ty{
    int l,r,v,len;
    ty()=default;
    ty(int l,int r,int v,int len):l(l),r(r),v(v),len(len){}
}a[maxn];

struct node{
    array<int,2> t;
    int id,op;
    node(array<int,2> t,int id,int op):t(t),id(id),op(op){}
    bool operator<(const node& x)const{
        if(t[0]*x.t[1]!=t[1]*x.t[0]) return t[0]*x.t[1]<t[1]*x.t[0];
        return op>x.op;
    }
};
int n,x;
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);
    cin>>n>>x;
    vector<node> event;
    for(int i = 1;i<=n;++i){
        int l,r,v;
        cin>>l>>r>>v;
        a[i]=ty(l,r,v,r-l);
        array<int,2> time={0,1};
        if(r<x){
            time =  {x-r,v};
            event.push_back(node(time,i,1));
            time =  {x-l,v};
            event.push_back(node(time,i,0));//删除前一刻
        }else{
            if(l<=x){
                event.push_back(node(time,i,1));//0时刻就在
                time = {x-l,v};
                event.push_back(node(time,i,0));//删除前一刻
            }
        }
    }

    sort(event.begin(),event.end());
    set<array<int,2>> se;
    int ans = 0;
    vector<array<int,2>> del;
    for(int l = 0,r = 0;l<(event.size());){
        r = l;
        while(!del.empty()){
            if(!se.empty()) se.erase(del.back());
            del.pop_back();
        }
        if(!se.empty()) ans = max(ans,(*se.begin())[0]);
        while(r<event.size()&&event[l].t[0]*event[r].t[1]==event[l].t[1]*event[r].t[0]){
            int len = a[event[r].id].len;
            if(event[r].op==1) se.insert({len,event[r].id});
            else del.push_back({len,event[r].id});
            ++r;
        }
        if(!se.empty()) ans = max(ans,(*se.begin())[0]);
        l = r;
    }
    cout<<ans<<"\n";
    return 0;
}

H

给定 哈希模数和底数 问求哈希冲突的概率 ,答案显然是 哈希值一样的对数减去 真正一样的对数,真正一样的对数我们可以自己写一个hash,形成双hash ,双指针求解一下即可

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;
#define int long long

constexpr int maxn = 3e3+10;
constexpr int B = 133;
constexpr i64 mod = 10000000000000061;
int n,p,m;
i64 h[maxn],base[maxn],s[maxn];
int get(int l,int r){
    return ((i128)h[r]-(i128)h[l-1]*base[r-l+1]%mod+mod)%mod;
}

vector<int> mp;
vector<int> v;
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);
    cin>>n>>p>>m;
    base[0]=1;
    for(int i = 1;i<=n;++i){
        cin>>s[i];
        base[i] = base[i-1]*B%mod;
        h[i] = (1LL*h[i-1]*B%mod+s[i])%mod;
    }
    for(int l =1;l<=n;++l){
        int a=0,b_p=1;
        for(int r = l;r<=n;++r){
            a = (1LL*a+1LL*s[r]*b_p%m)%m;
            b_p = 1LL*b_p*p%m;
            int b = get(l,r);
            mp.emplace_back(a);
            v.emplace_back(b);
        }
    }

    i64 ans = 0;
    sort(mp.begin(),mp.end());
    for(int i = 0;i<mp.size();){
        int j = i;
        while(j+1<mp.size()&&mp[i]==mp[j+1]) ++j;
        ans+=(j-i+1)*(j-i);
        i = j+1;
    }

    sort(v.begin(),v.end());
    for(int i = 0;i<v.size();){
        int j = i;
        while(j+1<v.size()&&v[i]==v[j+1]) ++j;
        ans-=(j-i+1)*(j-i);
        i = j+1;
    }
    cout<<ans<<"\n";
    return 0;
}

J

去年写的已经忘记了,看个码

#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define int long long
const int mod = 998244353;
int n,m;

int qp(int a,int b){
    int res = 1;
    while(b){
        if(b&1) res = (res*a)%mod;
        b>>=1;
        a = (a*a)%mod;
    }
    return res;
}

signed main(){
    ios;
    cin>>n>>m;
    vector<int> x(n),y(m),px(n),py(m);
    for(int i =0;i<n;++i){
        cin>>x[i];
        px[i] = x[i]+i*20LL;
    }
    for(int i =0;i<m;++i){
        cin>>y[i];
        py[i] = y[i]+i*20LL;
    }
    int j=0;
    i64 ans = 0;
    for(int k = 0;k<n;++k){
        while(py[j]<px[k]&&j<m){
            j++;
        }
        ans = (ans+qp(2,m-j)*qp(2,n-k-1))%mod;
    }
    cout<<ans<<"\n";
    return 0;
}

K

x + 0.5 x+0.5 x+0.5 不好处理我们把他平移到 x x x 处,对于伤害我们把它移到点上面去,题目中给的例子[3,4]和[4,5] 我们看成 hurt[3,4,5]={1,2,1} 这样方便计算,类似于 D D D 的处理方法,发现一段区间的长度是固定的 这样最多分成 2 ∗ n 2*n 2n 个区间,那么我们对数轴进行差分。如何精准知道死亡时间呢,我们可以二分线段,看他是在哪死的,就可以了,不过有些细节,出现的那段的伤害和最后死亡的那段不一定吃满的,需要精确计算一下,其余只要前缀和计算一下即可

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;

vector<array<int,2>> v;
vector<i64> hurt,pre,duan_hurt;
vector<array<i64,2>> ani;
vector<array<int,2>> duan;

signed main(){
    ios::sync_with_stdio(0);cin.tie(0);
    int n,m;
    cin>>n>>m;
    for(int i = 1;i<=n;++i){
        int l,r,w;
        cin>>l>>r>>w;
        v.push_back({l,w});
        v.push_back({r+1,-w});
    }

    for(int i = 1;i<=m;++i){
        i64 x,h;
        cin>>x>>h;
        ani.push_back({x,h});
    }
    sort(v.begin(),v.end());
    i64 now =0,lst =0;
    for(int i = 0;i<v.size();){
        duan_hurt.emplace_back(now);
        hurt.emplace_back((v[i][0]-lst)*now);
        duan.push_back({lst,v[i][0]-1});
        int j = i;
        while(j<v.size()&&v[j][0]==v[i][0]){
            now+=v[j][1];
            ++j;
        }
        lst = v[i][0];
        i = j;
    }

    i64 sum = 0;
    for(auto &i:hurt){
        sum+=i;
        pre.emplace_back(sum);
    }
    vector<int> ans;
    for(auto &[x,h]:ani){
        auto p = lower_bound(duan.begin(),duan.end(),array<int,2>{x+1,x})-duan.begin()-1;//第一段
        i64 first_hurt = (duan[p][1]-x)*duan_hurt[p];
        if(first_hurt>=h){
            ans.emplace_back((h+duan_hurt[p]-1)/duan_hurt[p]);
            continue;
        }
        if(first_hurt+pre.back()-pre[p]<h){
            ans.emplace_back(-1);
            continue;
        }
        int l = p+1, r = pre.size()-1;
        while(l<=r){
            int mid =(l+r)>>1;
            if(pre[mid]-pre[p]+first_hurt>=h) r = mid-1;
            else l = mid+1;
        }

        //pre[r]-pre[p]+first<h
        i64 rem = h-first_hurt-(pre[r]-pre[p]);
        i64 seconds = (rem+duan_hurt[l]-1)/duan_hurt[l];

        if(seconds*duan_hurt[l]>hurt[l]) ans.emplace_back(-1);
        else ans.emplace_back(seconds+duan[r][1]-x);
    }
    for(auto &i:ans) cout<<i<<"\n";
    return 0;
}

M

观察发现,层与层之间没有关系,手模样例发现,肯定是优先在我自己的子树内匹配掉最后,否则,两个节点会至少多走长度4,比较暴力的想法就是dfs,每次返回未匹配节点的深度和节点个数,在我当前子树内尝试将未匹配节点匹配,这样复杂度最坏 n 2 n^2 n2 怎么优化呢,我们发现每次最多未匹配的节点最多一个,似乎暗示着那我们是不是只要向上传递深度了,合并的过程可以通过启发式合并,我合并需要找到子节点 y 2 y_2 y2 是否含有子节点 y 1 y_1 y1 的深度,比较简单的数据结构就是set,那这样的时间复杂度是 n l o g 2 nlog^2 nlog2 ,足以通过本题

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;

constexpr int maxn = 1e5+10;
vector<int> g[maxn];
set<int> se[maxn];
int n,dep[maxn];
i64 ans =0;
void dfs(int x,int fa){
    dep[x] = dep[fa]+1;
    se[x].insert(dep[x]);
    if(g[x].size()==1&&fa!=0) return;
    for(auto &y:g[x]){
        if(y==fa) continue;
        dfs(y,x);
    }
    for(auto y:g[x]){
        if(y==fa) continue;
        if(se[x].size()<se[y].size()) se[x].swap(se[y]);
        for(auto z:se[y]){
            auto p = se[x].lower_bound(z);
            if(p==se[x].end()||*p!=z) se[x].insert(z);
            else{
                se[x].erase(p);
                ans+=2LL*abs(z-dep[x]); 
            }
        }
    }
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);
    cin>>n;
    for(int i = 1;i<n;++i){
        int x,y;
        cin>>x>>y;
        g[x].emplace_back(y);
        g[y].emplace_back(x);
    }
    dfs(1,0);
    cout<<ans<<"\n";
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值