题目
链接: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) (1≤n≤105)
思路:
-
二分答案: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} 2n−r−1,即 ( n − r ) × 2 n − r − 1 (n-r)\times 2^{n-r-1} (n−r)×2n−r−1 。
- 然后考虑基内的 r 个元素的影响。枚举每一个基内元素 y,如果剩下的 n -1 个元素可以构成 r 个基,那么就说明这个 y 是可以被替代的(即可以放在基外),那么它的贡献也是 2 n − r − 1 2^{n-r-1} 2n−r−1 。
#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 i∈A 和 j ∈ B j\in B j∈B ,满足 x i ≥ x j , y i ≤ y j x_i\ge x_j,y_i\le y_j xi≥xj,yi≤yj 。求 ∑ i ∈ A a i + ∑ j ∈ B b j \sum_{i\in A} a_i+\sum_{j\in B } b_j ∑i∈Aai+∑j∈Bbj 的最大值。
思路:简而言之,就是 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[i−1][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=1i−1dp[i][j]+b[i],(yj≤yi)
-
注意一个细节,将 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>yj,yk,xk<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