2021牛客暑期多校训练营4

2021牛客暑期多校训练营4

传送门

B.Sample Game


概率dp,挖坑


LCS


按LCS大小顺序,模拟即可,代码不美观就不发了。

E.Tree Xor


思路:

可知我们先令 W i = 0 W_i=0 Wi=0​​,可得节点 W i = { W 1 , W 2 . . . W n } W_i=\{W_1,W_2...W_n\} Wi={W1,W2...Wn}​ 。

易知当 W 1   x o r   X W_1\ xor\ X W1 xor X​​​​​,之后所以 W i W_i Wi​都会异或上X,那么我们需计算以下不等式。

L i ≤ W i   x o r   X ≤ R i L_i\le W_i\ xor\ X\le R_i LiWi xor XRi,X能取的合法值。

L i   x o r   W i ≤ X ≤ R i   x o r   W L_i\ xor\ W_i\le X\le R_i\ xor\ W Li xor WiXRi xor W

这样我们只需求 [ L i , R i ] x o r   W i [L_i,R_i]xor\ W_i [Li,Ri]xor Wi的区间交,即可算出答案。

线段树维护

我们可能看出, [ L i , R i ]   x o r   W i [L_i,R_i]\ xor\ W_i [Li,Ri] xor Wi会被分割成若干个不连续的区间。通过二进制的异或个规律我们可以发现,如果我们建棵 [ 0 , 2 30 − 1 ] [0,2^{30}-1] [02301]​的线段树区间,那么线段树分割的区间将全为连续的区间。那么我们用线段树求出异或后的区间,最后求区间交算答案。

代码:

#include <iostream>
#include <algorithm>
#include <vector>
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pll;
const int N=1e5+7;
const int S=(1<<30)-1;//建区间为[0,2^30-1]的线段树
struct node{int to,w,next;}e[2*N];
int n,a[N],ll[N],rr[N];
int head[N],tot;
vector <pll> ho,col;
void add(int u,int v,int w){
    e[++tot].to=v;
    e[tot].w=w;
    e[tot].next=head[u];
    head[u]=tot;
}
void dfs(int p,int fa){
    for(int i=head[p];i;i=e[i].next){
        int v=e[i].to,w=e[i].w;
        if(v==fa) continue;
        a[v]=a[p]^w;
        dfs(v,p);
    }
}
//处理[l,r] xor X
void ikuzo(int l,int r,int d,int x){
    //d为有多少位值不同。
    int pl=l&(S^((1<<d)-1));
    int pe=x&(S^((1<<d)-1));
    ho.push_back(pll(pe^pl,pe^pl+((1<<d)-1)));
}
void update(int l,int r,int ql,int qr,int d,int x){
    if(ql<=l&&r<=qr){
        ikuzo(l,r,d,x);
        return;
    }
    int mid=(l+r>>1);
    if(ql<=mid)   update(l,mid,ql,qr,d-1,x);
    if(mid+1<=qr) update(mid+1,r,ql,qr,d-1,x);
}
void solve(){
    //差分求区间交
    for(int i=0;i<ho.size();i++){
        col.push_back(pll(ho[i].fi,1));
        col.push_back(pll(ho[i].se+1,-1));
    }
    sort(col.begin(),col.end());
    int sum=0,ans=0;
    for(int i=0;i+1<col.size();i++){
        sum+=col[i].se;
        if(sum>=n){
            ans+=col[i+1].fi-col[i].fi;
        }
    }
    printf("%d\n",ans);
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)   cin>>ll[i]>>rr[i];
    for(int i=1;i<=n-1;i++){
        int u,v,w;
        cin>>u>>v>>w;
        add(u,v,w);add(v,u,w);
    }
    dfs(1,-1);
    for(int i=1;i<=n;i++){
        update(0,S,ll[i],rr[i],30,a[i]);
    }
    solve();
}

F.Just a joke


思路:

通过并查集算环和连通块。

假设连通块无环,即消除需奇数次。

假设连通块有环,为1+连通块环数。

我们算出连通块数+环数判奇偶即可算出谁必胜

代码:

#include <iostream>
using namespace std;
const int N=1e3+7;
int n,m,f[N];
void init(){
    for(int i=1;i<=n;i++){
        f[i]=i;
    }
}
int fin(int x){return f[x]==x? x:f[x]=fin(f[x]);}
int bing(int x,int y){
    int f1=fin(x),f2=fin(y);
    if(f1==f2) return 1;
    f[f2]=f1;
    return 0;
}
int main(){
    cin>>n>>m;
    init();
    int cnt=0;
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        if(bing(u,v)){
            cnt++;
        }
    }
    for(int i=1;i<=n;i++){
        if(fin(i)==i) cnt++;
    }
    if(cnt&1) cout<<"Alice\n";
    else cout<<"Bob\n";
}

I.Inverse Pair


思路:

我们可知,由于序列是个排列,所以我们对原序列进行+1,只能让原序列序列逆序对-1,即序列多出一对相等的数,所以我们从前往后遍历,如果序列+1能与之前某个元素相等,那么就逆序对-1。

代码:

#include <iostream>
using namespace std;
typedef long long ll;
const int N=2e5+7;
const int S=2e5;
int n,a[N],tree[N],vis[N];
int lowbit(int i){return i&(-i);}
void add(int p){
    for(int i=p;i<=S;i+=lowbit(i)){
        tree[i]++;
    }
}
int ask(int p){
    int res=0;
    for(int i=p;i;i-=lowbit(i)){
        res+=tree[i];
    }
    return res;
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    ll res=0;
    for(int i=n;i>=1;i--){
        res+=ask(a[i]);
        add(a[i]);
    }
    for(int i=1;i<=n;i++){
        if(vis[a[i]+1]){
            res--;
        }else{
            vis[a[i]]++;
        }
    }
    cout<<res<<"\n";
}


J.Average

思路:

题目问求 W i , j W_{i,j} Wi,j​​矩阵的长宽至少为 X , Y X,Y X,Y​​子矩阵的最大平均值,我们可以发现其平均值为 a i a_i ai的​​子序列长度至少为X的最大平均值与 b i b_i bi的子序列长度至少为​​Y的最大平均值之和。

二分答案

我们可以通过二分最大平均值答案,假设二分答案为mid

之后建立 v i = a i − m i d v_i=a_i-mid vi=aimid​​的前缀和,算出 M a x { p r e i − p r e j } Max\{ pre_i-pre_j\} Max{preiprej}​,且 i − j i-j ij​大于至少的长度限制。

如果大于等于0,则返回 t r u e true true

代码:

#include <iostream>
using namespace std;
const int N=1e5+7;
int n,m,x,y,a[N],b[N];
double v[N];
bool check(int a[],int sz,double mid,int p){
    for(int i=1;i<=sz;i++){
        v[i]=v[i-1]+double(a[i]-mid);
    }
    double mn=1e10,ans=-1e10;
    for(int i=p;i<=sz;i++){
        mn=min(mn,v[i-p]);
        ans=max(ans,v[i]-mn);

    }
    return ans>=0;
}
double solve(int a[],int sz,int p){
    double l=0,r=100000,ans=0;
    for(int i=0;i<=100;i++){
        double mid=(l+r)/2.0;
        if(check(a,sz,mid,p)) ans=mid,l=mid;
        else r=mid;
    }
    return ans;
}
int main(){
    cin>>n>>m>>x>>y;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=m;i++) scanf("%d",&b[i]);
    printf("%.10lf",solve(a,n,x)+solve(b,m,y));
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值