[63] (多校联训) A层冲刺NOIP2024模拟赛19

lhx 对 \((\ln n)^{\ln n}\) 求导求出一个形如 \(\frac{1}{n\ln n\ln\ln n}\) 的东西

A.图书管理

说一种很好玩的 \(n^2\log n\) 做法(因为动态中位数我只会这个)

对顶堆:

就是你开一个大根堆和一个小根堆,然后把它们怼在一起,钦定比中位数大的放小根堆,小的放大根堆,这样中间就是中位数

关于怎么才能知道基准是哪个数,其实你没必要知道基准是哪个数,因为你这是两个堆顶在一块了,你完全可以根据堆的特性来做,现在你只需要维护两个堆大小的平衡,这样就能保证找到中位数,加入插入某个数之后不平衡了,那么直接弹出较大的堆的堆顶,插入另一个堆(这样你还能保证两个堆的值域不交,除非堆顶相等,证明考虑直接想这个对顶堆的性质)

因此这玩意实现了动态插入,平衡,寻找中位数,可以用优先队列实现堆,复杂度均挂 \(\log\)\(2s\) 的话是稳过的,如果值域很大的话,这个做法是相当优秀的那一种

(UPD: 忘了,值域很大可以离散化,那就 \(n\) 比较大的情况,在单调插入与查询中位数的情况下比较优)

对顶堆写法
#include<bits/stdc++.h>
using namespace std;
template<typename T>
class single_mid_t{
    private:
        priority_queue<T,vector<T>,less<T>>p1;
        priority_queue<T,vector<T>,greater<T>>p2;
        inline void fixed(){
            while((int)p1.size()-(int)p2.size()>1){
                p2.push(p1.top());
                p1.pop();
            }
            while((int)p1.size()-(int)p2.size()<1){
                p1.push(p2.top());
                p2.pop();
            }
        }
    public:
        inline void insert(T x){
            p1.push(x);
            fixed();
        }
        inline T askmid(){
            return p1.top();
        }
};
int n;
long long ans;
int a[10001];
single_mid_t<int>s[10001];
int main(){
    scanf(n);
    for(int i=1;i<=n;++i){
        scanf(a[i]);
    }
    for(int i=1;i<=n;++i){
        ans+=1ll*i*i*a[i];
        s[i].insert(a[i]);
        for(int j=i+1;j+1<=n;j+=2){
            s[i].insert(a[j]);s[i].insert(a[j+1]);
            ans+=1ll*i*(j+1)*s[i].askmid();
        }
    }
    cout<<ans;
}

然后说 \(n^2\) 的正解

因为这个题是排列,你可以枚举中位

钦定一个数 \(a_i\) 是中位数,考虑位每个地方赋值,令 \(a_j\lt a_i\) 处的 \(j\) 的值与 \(a_j\gt a_i\) 处的 \(j\) 的值相等,问题转化为求包含 \(i\) 的区间 \([l,r]\) 数量,满足区间和为 \(0\)

还是一样的,如果你钦定每处值的绝对值是 \(1\) 的话,这个值还是不会超过 \([-n,n]\),因此仍然考虑开桶,内层也可以 \(O(n)\) 做出来

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int dx=10000;
int n,ans;
int a[20001],cnt[20001];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
        cin>>a[i];
    }
	for(int i=1;i<=n;i++){
	    int tot=0;
        for(int j=i;j<=n;++j){
            tot+=(a[j]>a[i])-(a[j]<a[i]);
            cnt[tot+dx]+=j;
        }
        tot=0;
	    for(int j=i;j>=1;--j){
            tot-=(a[j]>a[i])-(a[j]<a[i]);
            ans+=j*cnt[tot+dx]*a[i];
        }
        tot=0;
	    for(int j=i;j<=n;++j){
            tot+=(a[j]>a[i])-(a[j]<a[i]);
            cnt[tot+dx]&=0;
        }
	}
	cout<<ans;
}

B.两棵树

连通块数=剩余的点数−剩余的边数

贡献被拆成四个部分:点×点−边×点−点×边+边×边

  1. 点×点:选择 \(x\in T,u\in U\),当 \(x\neq u\) 均保留概率 \(\frac{1}{4}\),当 \(x=u\) 概率为 \(0\),所以期望为 \(\frac{n(n-1)}{4}\)

  2. 边×点:选择 \((x,t)\in T,u\in U\),当 \(x\neq u,y\neq u\) 均保留概率 \(\frac{1}{8}\),其余情况概率为 \(0\),所以期望为 \(\frac{(n-1)(n-2)}{8}\)

  3. 边×边:选择 \((x,y)\in T,(u,v)\in U\),当 \(x,y,u,v\) 互不相同时,概率为 \(frac{1}{16}\),其余情况概率为 \(0\),可以枚举所有 \((x,y)\),计算符合条件的边 \((u,v)\) 数量:\(n-1-\deg_Ux-\deg_Uy\),如果 \(U\) 存在 \((u,v)=(x,y)\) 则加一

奇怪的是似乎和我推出来不是很一样,为啥我推完之后剩一个 \(\frac{(n-1)^2}{2}\)

UPD: 哦知道为啥了,应该是 \(\frac{n(n-1)}{4}-2\frac{(n-1)(n-2)}{8}\) 而不是 \(\frac{n(n-1)}{4}+2\frac{(n-1)(n-2)}{8}\)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=998244353;
inline int constexpr power(int a,int t=p-2){
    int ans=1,base=a;
    while(t){
        if(t&1){
            ans=ans*base%p;
        }
        base=base*base%p;
        t>>=1;
    }
    return ans;
}
int n,ans;
vector<int>t[200001],u[200001];
int fa[200001];
void dfs(int now,int last){
    fa[now]=last;
    for(int i:u[now]){
        if(i!=last){
            dfs(i,now);
        }
    }
}
signed main(){
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n-1;++i){
        int x,y;cin>>x>>y;
        t[x].push_back(y);
        t[y].push_back(x);
    }
    for(int i=1;i<=n-1;++i){
        int x,y;cin>>x>>y;
        u[x].push_back(y);
        u[y].push_back(x);
    }
    ans=(n-1)%p*power(2)%p;
    dfs(1,0);
    int res=0;
    for(int i=1;i<=n;++i){
        for(int j:t[i]){
            if(j>i){
                res=(res+n-1)%p;
                res=((res-(int)u[i].size()-(int)u[j].size())%p+p)%p;
                if(fa[i]==j or fa[j]==i) res=(res+1)%p;
            }
        }
    }
    ans=(ans+res*power(16)%p)%p;
    cout<<ans;
}

C.函数

好题

首先可以做有无解的判定

零点存在定理,找到所有数中 \(x_i\operatorname{xor}a-b\) 的最大值和最小值,如果这两个值同号则一定不存在解,异或值最大最小可以 01-trie 解决

然后继续零点存在定理,我们可以找到最大和最小的两个位置,现在这两个位置一定不同号,那么可以确定的是中间总存在一个位置合法(两个异号中间总有大于一个异号的连接点)

因此我们只需要维持这种端点异号的关系,每次二分中间点的值,每次用这个点替换同号的端点,最终一定会找到一个断点是合法的

#include<bits/stdc++.h>
using namespace std;
#define int long long
struct trie{
    signed to[1000001*32][2];
    signed id[1000001*32];
    signed cnt=0;
    inline const string to_string(int x){
        string ans;
        while(x){
            ans.push_back(x%2+'0');
            x/=2;
        }
        while(ans.size()!=31ull) ans.push_back('0');
        reverse(ans.begin(),ans.end());
        return ans;
    }
    inline void insert(int x,int _id){
        int pos=0;
        string y=to_string(x);
        for(char i:y){
            if(!to[pos][i-'0']){
                to[pos][i-'0']=++cnt;
            }
            pos=to[pos][i-'0'];
        }
        id[pos]=_id;
    }
    inline pair<int,int> askmax(int x){
        int pos=0,ans=0;
        string y=to_string(x);
        for(char i:y){
            ans*=2;
            if(to[pos][1-(i-'0')]){
                pos=to[pos][1-(i-'0')];
                ans++;
            }
            else{
                pos=to[pos][i-'0'];
            }
        }
        return {id[pos],ans};
    }
    inline pair<int,int> askmin(int x){
        int pos=0,ans=0;
        string y=to_string(x);
        for(char i:y){
            ans*=2;
            if(to[pos][i-'0']){
                pos=to[pos][i-'0'];
            }
            else{
                pos=to[pos][1-(i-'0')];
                ans++;
            }
        }
        return {id[pos],ans};
    }
};
trie t;
int n,q;
int x[1000001];
inline bool check(int i,int a,int b){
    return ((x[i]^a)-b)*((x[i+1]^a)-b)<=0;
}
inline int f(int x,int a,int b){
    return (x^a)-b;
}
void bl(){
    for(int i=1;i<=n;++i){
        cin>>x[i];
    }
    while(q--){
        int a,b;
        cin>>a>>b;
        bool flag=false;
        for(int i=1;i<n;++i){
            if(f(x[i],a,b)*f(x[i+1],a,b)<=0){
                cout<<i<<" ";
                flag=true;
                break;
            }
        }
        if(!flag) cout<<-1;
        cout<<'\n';
    }
}
signed main(){
    ios::sync_with_stdio(false);
    cin>>n>>q;
    for(int i=1;i<=n;++i){
        cin>>x[i];
        t.insert(x[i],i);
    }
    while(q--){
        int a,b;
        cin>>a>>b;
        auto tmpa=t.askmax(a),tmpb=t.askmin(a);
        if(n==1 or f(x[tmpa.first],a,b)*f(x[tmpb.first],a,b)>0){
            cout<<-1<<'\n';
        }
        else{
            if(tmpa.first>tmpb.first) swap(tmpa,tmpb);
            int l=tmpa.first,r=tmpb.first;
            while(l!=r-1){
                int mid=(l+r)/2;
                if(f(x[mid],a,b)<=0){
                    if(tmpa.second-b<=0){
                        l=mid;
                    }
                    else{
                        r=mid;
                    }
                }
                else{
                    if(tmpa.second-b>0){
                        l=mid;
                    }
                    else{
                        r=mid;
                    }
                }
            }
            cout<<l<<'\n';
        }
    }
}

这是什么

posted @ 2024-11-07 21:35  HaneDaniko  阅读(35)  评论(4编辑  收藏  举报