2019牛客暑期多校训练营(第一场)

链接:https://ac.nowcoder.com/acm/contest/881#question

A Equivalent Prefixes (二分 + 区间最值查询)

题意:给定两个长度为 n 的数组 a 和 b ,找到一个最大的位置 p ,使得两个数组在 [1,p] 上的任意一个子区间上的最小值的位置都相同。 ( 1 ≤ n ≤ 1 0 5 ) (1\le n \le 10^5) (1n105)

思路

  • 二分答案:p 之前的位置都是符合的,p 之后的位置都是不符合的。

  • 所以二分这个位置 p 即可。check 的时候,只需要找到最小值的位置,然后拆分成两个更小的区间 check 就好了。

  • 也可以拿单调找做,维护一个单调递增的栈,然后找到这两个数组跑出来的索引都相同的最大位置,就是答案。

#include <bits/stdc++.h>
#define pii pair<int,int>
#define se second
#define ll long long
using namespace std;
const int maxn=1e5+5;
int n,a[maxn],b[maxn];
pii dp1[maxn][20],dp2[maxn][20];
int Log[maxn];
void init(pii dp[][20],int f[])
{
    for(int i=1; i<=n; ++i) dp[i][0]= {f[i],i};
    for(int j=1; (1<<j)<=n; ++j)
        for(int i=1; i+(1<<j)-1<=n; ++i)
            dp[i][j]=min(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
}
pii queryMin1(int L,int R)
{
    int k=Log[R-L+1];
    return min(dp1[L][k],dp1[R-(1<<k)+1][k]);
}
pii queryMin2(int L,int R)
{
    int k=Log[R-L+1];
    return min(dp2[L][k],dp2[R-(1<<k)+1][k]);
}
bool check(int L,int R)
{
    if(L>=R) return 1;
    pii p1=queryMin1(L,R);
    pii p2=queryMin2(L,R);
    if(p1.se!=p2.se) return 0;
    return check(L,p1.se-1)&&check(p1.se+1,R);
}
int main()
{
    Log[1]=0;
    for(int i=2; i<maxn; ++i) Log[i]=Log[i/2]+1;
    while(cin>>n)
    {
        for(int i=1; i<=n; ++i) cin>>a[i];
        for(int i=1; i<=n; ++i) cin>>b[i];
        init(dp1,a);
        init(dp2,b);
        int L=1,R=n;
        while(L<R)
        {
            int mid=(L+R+1)>>1;
            if(check(1,mid)) L=mid;
            else R=mid-1;
        }
        printf("%d\n",L);
    }
    return 0;
}

E ABBA (DP计数)

题意:给定 n + m 个 ‘A’ 和 ‘B’ ,问有多少种排列可以分成 n 个‘AB’的子序列,和 m 个 ‘BA’ 的子序列。对答案除以 1e9+7。

思路:设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示放了 i 个 ‘A’, j 个’B’ 的方案数。我们在放 ‘A’ 的时候,优先放的是 ‘AB’ 的 ‘A’ ,此时需要满足的条件是 i-1<n 。在 ‘AB’ 的 ‘A’ 放完了之后,才考虑 ‘BA’ 的 ‘A’。此时需要满足的是 i-1-n<j 。意思是说, ‘AB’ 的 ‘A’ 放完了之后,后面的都是 ‘BA’ 的 ‘A’ ,此时放的 ‘A’ 要小于 ‘B’ 的数量。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2010,mod=1e9+7;
int n,m;
int dp[maxn][maxn];
void add(int &x,int y)
{
    x+=y;
    if(x>=mod) x-=mod;
}
int main()
{
    while(cin>>n>>m)
    {
        for(int i=0; i<=n+m+1; ++i)
            for(int j=0; j<=n+m+1; ++j)
                dp[i][j]=0;
        dp[0][0]=1;
        for(int i=0; i<=n+m; ++i)
            for(int j=0; j<=n+m; ++j)
            {
                if(i<n||i-n<j) add(dp[i+1][j],dp[i][j]);
                if(j<m||j-m<i) add(dp[i][j+1],dp[i][j]);
            }
        cout<<dp[n+m][n+m]<<"\n";
    }
    return 0;
}

H XOR (线性基)

题意:给定一个长度为 n 的数组 a 。求所有异或和为 0 的子集的大小的和。

思路:求出每一个子集是不可能的,考虑每一个数的贡献。它能够在的集合的数量。

  • 首先 n 个元素,构成 r 个基底。基外有 n - r 个元素,假设选中一个基外元素 x ,那么 x 和剩下的 n-r-1 个元素任意构成的子集。都可以由基内的元素异或得到。所有对基外的 n - r 个元素都有 2 n − r − 1 2^{n-r-1} 2nr1,即 ( n − r ) × 2 n − r − 1 (n-r)\times 2^{n-r-1} (nr)×2nr1
  • 然后考虑基内的 r 个元素的影响。枚举每一个基内元素 y,如果剩下的 n -1 个元素可以构成 r 个基,那么就说明这个 y 是可以被替代的(即可以放在基外),那么它的贡献也是 2 n − r − 1 2^{n-r-1} 2nr1
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,mod=1e9+7;
int n;
ll a[maxn],b[maxn],cnt;
struct LB
{
    ll b[64],p[64],cnt;
    LB()
    {
        memset(b,0,sizeof(b));
        memset(p,0,sizeof(p));
        cnt=-1;
    }
    bool insert(ll x)
    {
        for(int i=62; i>=0; --i)
        {
            if(x>>i&1)
            {
                if(!b[i])
                {
                    b[i]=x;
                    cnt++;
                    return 1;
                }
                x^=b[i];
            }
        }
        return x>0;
    }
};
int qpow(int b,int n,int mod)
{
    int res=1;
    while(n>0)
    {
        if(n&1) res=1ll*res*b%mod;
        b=1ll*b*b%mod;
        n>>=1;
    }
    return res;
}
void add(int &x,int y)
{
    x+=y;
    if(x>=mod) x-=mod;
}
int main()
{
    while(~scanf("%d",&n))
    {
        LB lb1,lb2;
        cnt=0;
        int num=0;
        for(int i=1; i<=n; ++i)
        {
            scanf("%lld",&a[i]);
            if(lb1.insert(a[i])) b[++cnt]=a[i];
            else lb2.insert(a[i]),num++;
        }
        int res=qpow(2,num-1,mod);
        int ans=1ll*num*res%mod;
        for(int i=1; i<=cnt; ++i)
        {
            LB lb3=lb2;
            for(int j=1; j<=cnt; ++j)
            {
                if(i==j) continue;
                lb3.insert(b[j]);
            }
            if(lb3.cnt==lb1.cnt) add(ans,res);
        }
        printf("%d\n",ans);
    }
    return 0;
}

I Points Division (线段树维护DP)

题意:给定 n 个点有 a i , b i a_i,b_i ai,bi 两个属性,划分成两个集合。其中不存在任意一个点 i ∈ A i \in A iA j ∈ B j\in B jB ,满足 x i ≥ x j , y i ≤ y j x_i\ge x_j,y_i\le y_j xixj,yiyj 。求 ∑ i ∈ A a i + ∑ j ∈ B b j \sum_{i\in A} a_i+\sum_{j\in B } b_j iAai+jBbj 的最大值。

思路:简而言之,就是 B 集合中的任意一个点的右下角都不存在 A 集合中的点。那么 B 集合中的点,就构成了一条单调不降的折线。

  • 将点集按 x 轴排序,设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i 个点,且第 j 个点在折线上的最大权值。(可以看做第 j 个点的横线是当前最高的线)

  • 那么加入的第 i 个点的贡献是: d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + ( y i > y j ? a i : b i ) dp[i][j]=dp[i-1][j] + (y_i>y_j? a_i: b_i) dp[i][j]=dp[i1][j]+(yi>yj?ai:bi)

  • 同样的 d p [ i ] [ i ] dp[i][i] dp[i][i] 就从前面的所有小于等于 y i y_i yi 的点转移过来。 d p [ i ] [ i ] = m a x j = 1 i − 1 d p [ i ] [ j ] + b [ i ] , ( y j ≤ y i ) dp[i][i]=max_{j=1}^{i-1}dp[i][j]+b[i],(y_j\le y_i) dp[i][i]=maxj=1i1dp[i][j]+b[i],(yjyi)

  • 注意一个细节,将 x 从小到大排序后,x 分量相同时,需要将 y 从大到小排序。假设, y i > y j , y k , x k < x i = x j y_i >y_j,y_k,x_k<x_i=x_j yi>yjykxk<xi=xj 对 k 点来说, j 点对它的贡献是 a j a_j aj。而如果先将 y j y_j yj 更新后,再更新 y i y_i yi ,那么在通过 k 点来更新 i 点时,是不合法的,因为此时的 j 点的贡献应该是 b j b_j bj 而不是 a j a_j aj

#include <bits/stdc++.h>
#define ls (rt<<1)
#define rs (rt<<1|1)
#define ll long long
using namespace std;
const int maxn=1e5+5;
int n;
struct Node
{
    int x,y,a,b;
    bool operator<(const Node& b) const
    {
        if(x==b.x) return y>b.y;
        return x<b.x;
    }
} p[maxn];
vector<int> ally;
ll st[maxn<<2],lazy[maxn<<2];
void pushDown(int rt)
{
    if(lazy[rt])
    {
        st[ls]+=lazy[rt];
        st[rs]+=lazy[rt];
        lazy[ls]+=lazy[rt];
        lazy[rs]+=lazy[rt];
        lazy[rt]=0;
    }
}
void pushUp(int rt)
{
    st[rt]=max(st[ls],st[rs]);
}
void build(int rt,int L,int R)
{
    st[rt]=lazy[rt]=0;
    if(L==R) return;
    int mid=(L+R)>>1;
    build(ls,L,mid);
    build(rs,mid+1,R);
}
void update(int rt,int l,int r,int L,int R,int val)
{
    if(l>r) return;
    if(l<=L&&R<=r)
    {
        st[rt]+=val;
        lazy[rt]+=val;
        return;
    }
    pushDown(rt);
    int mid=(L+R)>>1;
    if(l<=mid) update(ls,l,r,L,mid,val);
    if(r>mid) update(rs,l,r,mid+1,R,val);
    pushUp(rt);
}
void update(int rt,int pos,int L,int R,ll val)
{
    if(L==R)
    {
        st[rt]=max(st[rt],val);
        return;
    }
    pushDown(rt);
    int mid=(L+R)>>1;
    if(pos<=mid) update(ls,pos,L,mid,val);
    else update(rs,pos,mid+1,R,val);
    pushUp(rt);
}
ll queryMax(int rt,int l,int r,int L,int R)
{
    if(l<=L&&R<=r) return st[rt];
    pushDown(rt);
    int mid=(L+R)>>1;
    ll ans=-1e18;
    if(l<=mid) ans=max(ans,queryMax(ls,l,r,L,mid));
    if(r>mid) ans=max(ans,queryMax(rs,l,r,mid+1,R));
    return ans;
}
int main()
{
    while(~scanf("%d",&n))
    {
        ally.clear();
        for(int i=1; i<=n; ++i)
        {
            scanf("%d%d%d%d",&p[i].x,&p[i].y,&p[i].a,&p[i].b);
            ally.push_back(p[i].y);
        }
        ally.push_back(0);
        sort(ally.begin(),ally.end());
        ally.resize(unique(ally.begin(),ally.end())-ally.begin());
        int tot=ally.size();
        for(int i=1; i<=n; ++i)
        {
            int pos=lower_bound(ally.begin(),ally.end(),p[i].y)-ally.begin()+1;
            p[i].y=pos;
        }
        build(1,1,tot);
        update(1,1,1,tot,0);
        sort(p+1,p+1+n);
        for(int i=1; i<=n; ++i)
        {
            ll mx=queryMax(1,1,p[i].y,1,tot);
            update(1,1,p[i].y-1,1,tot,p[i].a);
            update(1,p[i].y,tot,1,tot,p[i].b);
            update(1,p[i].y,1,tot,mx+p[i].b);
        }
        printf("%lld\n",st[1]);
    }
    return 0;
}

J Fraction Comparision(签到的签到)

题意:求 x a \frac xa ax y b \frac yb by 哪个大。
思路:化成真分数后比大小,或者直接用__int128

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值