ALGORITHM NOTE / ARCHIVE

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

期望具有线性性,可以将第一次选取的数为为 的期望算出来,再把他们相加除以 就是答案,问题转化为 对于第一次选取的数 的期望长度怎么算, 对于一个 ,设 是以 开始的期望长度,我们手模几个样例,发现如果选出来的数 , , 对于 , ,如何理解相当于从 开始已经长度为 1 了,我只能选择比 大的数,对于 每个数我有 的概率选中它。因为期望具有线性性,所以求和相加除以平均数+1就是以 开始的期望长度,但是这还不足以通过本题,如果每次跑一遍递推就是 的复杂度,发现还可以优化,发现 式子好像是一个后缀和的形式,那么可以维护后缀和 ,那么每次的递推不就变成了 , ,我们要求的 已经被包含在最后的 里面,那么就不需要 的递推了,只需要维护 即可, 发现是一次函数复合. 设 ,,写成映射形式就是 ,那么我们只要根据这个维护关于 一次函数的嵌套即可求解,需要注意的是维护的嵌套顺序不对,需要求解上面映射的逆映射,这题卡空间,不清楚是否可以通过,不过可以提一嘴逆映射怎么求。 复合类似于于左乘矩阵,求逆也是类似的。具体的,现有一次函数复合 ,现在我想得到 ,那么我可以 , 其中 该题还有一个细节,因为 很大,需要线性求逆元

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

A

期望具有线性性,可以将第一次选取的数为为 的期望算出来,再把他们相加除以 就是答案,问题转化为 对于第一次选取的数 的期望长度怎么算, 对于一个 ,设 是以 开始的期望长度,我们手模几个样例,发现如果选出来的数 , , 对于 , ,如何理解相当于从 开始已经长度为 1 了,我只能选择比 大的数,对于 每个数我有 的概率选中它。因为期望具有线性性,所以求和相加除以平均数+1就是以 开始的期望长度,但是这还不足以通过本题,如果每次跑一遍递推就是 的复杂度,发现还可以优化,发现 式子好像是一个后缀和的形式,那么可以维护后缀和 ,那么每次的递推不就变成了 , ,我们要求的 已经被包含在最后的 里面,那么就不需要 的递推了,只需要维护 即可, 发现是一次函数复合. 设 ,,写成映射形式就是 ,那么我们只要根据这个维护关于 一次函数的嵌套即可求解,需要注意的是维护的嵌套顺序不对,需要求解上面映射的逆映射,这题卡空间,不清楚是否可以通过,不过可以提一嘴逆映射怎么求。 复合类似于于左乘矩阵,求逆也是类似的。具体的,现有一次函数复合 ,现在我想得到 ,那么我可以 , 其中 该题还有一个细节,因为 很大,需要线性求逆元

#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

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

#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

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

#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,每次返回未匹配节点的深度和节点个数,在我当前子树内尝试将未匹配节点匹配,这样复杂度最坏 怎么优化呢,我们发现每次最多未匹配的节点最多一个,似乎暗示着那我们是不是只要向上传递深度了,合并的过程可以通过启发式合并,我合并需要找到子节点 是否含有子节点 的深度,比较简单的数据结构就是set,那这样的时间复杂度是 ,足以通过本题

#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;
}