OI 笑传 #19
今天是一些 CF。
CF2152D
除二加一什么的当然要放到二进制上。
如果没有小 R,那么操作的次数就是二进制位数减一加起来。
观察一下发现小 R 的加一是很弱小的,因为小 P 除二可以把整个二进制往下拉(右移)。
继续观察可以大概看出来 R 最多让一个数贡献多一次,例如 \((1111)\) 这样的,加一下就可以让二进制位多一个。
但是不是都这样,如果长得是 \((10000)\) 这样的,加的速度一定没有除的快,也就是每加上一个 \(1\),就被右移顶掉了,最后最高位的 \(1\) 落下来,这个位就不能再操作了。
但是还有一种数长得是 \((1010)\) 这样,也就是非最高位的其它位还有 \(1\),我们在一个 \(1\) 落到最低位上之后可以直接加一个 \(1\) 把他顶上去,就是 \((101) +1 \rightarrow (110) \div 2 \rightarrow (11) +1 \rightarrow(100)\) 这样,最后也可以让二进制位多一个。
但是这样的数也不是都这样,比如 \((1001)\),如果现在是 P 操作,因为要最小化所以必须要打压这个最低位的 \(1\),直接除二把它扬了就是 \((100)\),此时是我们上文说过的情况。
但是如果现在是 R 操作,他由于要最大化就要把这个 \(1\) 顶上去,于是就是 \((1010)\),也变成了我们上文提到的情况。
于是现在 P 和 R 就要开始争抢这些 \((100001)\) 状的数,也就是所有 \(2^n+1\) 的数。争抢的结果是 R 可以抢到 \(\left \lfloor \frac{cnt}{2} \right \rfloor\) 这么多数把 \(1\) 顶上去。就是让这些数的贡献加 \(1\)。
第一发交的时候没想明白这一点,以为 P 可以一直打压 R 来着。
至于其它情况,手摸一下也知道都是没用的,因为每加上一个 \(1\),就会被右移顶掉。R 这么聪明是不会浪费这些的。再说如果可以不理会 P allin 一个数的话这题要上天了。这么唯心的证明也不是不可以(
代码很好写,询问把贡献前缀和一下即可。
code
Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
i64 x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=3e5+5;
int n,q;
i64 val[N];
i64 dy[N];
void init(){
for(int i=1;i<=n;i++){
val[i]=dy[i]=0;
}
return ;
}
void solve(){
cin>>n>>q;
for(int i=1;i<=n;i++){
int x;cin>>x;
if(x==3){
dy[i]=1;val[i]=1;
continue;
}
vector<int> d;
while(x){
int mod=x%2;
x=x>>1;
d.push_back(mod);
}
bool exs=0;
for(int j=1;j<d.size();j++){
if(d[j]==1&&j!=d.size()-1){
exs=1;val[i]=d.size();break;
}
}
if(exs==0&&d[0]==1) val[i]=d.size()-1,dy[i]=1;
else if(exs==0) val[i]=d.size()-1;
}
for(int i=1;i<=n;i++){
val[i]=val[i-1]+val[i];
dy[i]=dy[i-1]+dy[i];
}
while(q--){
int l,r;cin>>l>>r;
i64 ans=val[r]-val[l-1];
int ei=dy[r]-dy[l-1];
if(ei){
cout<<ans+ei/2<<'\n';
}
else cout<<ans<<'\n';
}
return ;
}
int main(){
int T;cin>>T;
while(T--){
init();solve();
}
return 0;
}
CF2140D
初见手摸了一些线段发现最后的答案好像和最后一个染的线段是那个有关系。
由此可以得到好像 \(n\) 为偶数的情况是直接做就行的,一个贪心的选线段方法是每次选左端点最左的和右端点最右的,然后累加进去答案,这个东西把左右端点排下序应该是好做的。
但是其实是不好做的,因为你有可能会同时选到同一个线段的左右端点。
于是我想能不能有二分定出来一个分界线,左边的交出自己的左端点,右边的交出右端点。又得到一个结论就是线段匹配的顺序是不重要的,重要的是每个线段用的是左还是右端点。
但是我草还是很难做啊,因为有些线段就是横跨一个很大的距离,问题还是可能会同时选到同一个线段的左右端点。
我谔谔那怎么办。
我们应用一下调整法,假设我们现在有两个集合 \(L,R\),表示交出自己左端点的线段和自己右端点的线段的集合。
从左边右边各取一个线段 \([l_1,r_1],[l_2,r_2]\)。
因为 \([l_1,r_1]\) 出左端点,\([l_2,r_2]\) 出右端点,而不是相反的。于是这两个线段显然应满足:
移个项呢?
也就是说,\(l+r\) 较大的那些线段出右端点,小的出左边。
这就是对的。这样我们就可以快速求解 \(n\) 为偶数的情况。
\(n\) 为奇数时,左右移动指针指的最后一个染的线段即可,每次改动都是 \(O(1)\) 的,取个最值就行。
code
Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
i64 x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=2e5+6;
int n;
struct line{
i64 l,r;
i64 s;
}li[N];
bool cmp(line x,line y){
return x.s<y.s;
}
void init(){
for(int i=0;i<=n;i++){
li[i].l=li[i].r=0;
}
return ;
}
void solve(){
cin>>n;
i64 base=0;
for(int i=1;i<=n;i++){
cin>>li[i].l>>li[i].r;
li[i].s=li[i].l+li[i].r;
base+=li[i].r-li[i].l;
}
sort(li+1,li+1+n,cmp);
i64 s1=0,s2=0;
if(n%2==0){
for(int i=1;i<=n/2;i++){
s1+=li[i].l;
}
for(int i=n/2+1;i<=n;i++){
s2+=li[i].r;
}
cout<<base+s2-s1<<'\n';
return ;
}
for(int i=1;i<=n/2;i++){
s1+=li[i].l;
}
for(int i=n/2+2;i<=n;i++){
s2+=li[i].r;
}
i64 ans=0;
ans=max(ans,base+s2-s1);
i64 b1=s1,b2=s2;
for(int i=n/2;i>=1;i--){
s1-=li[i].l;
s1+=li[i+1].l;
ans=max(ans,base+s2-s1);
}
s1=b1;s2=b2;
for(int i=n/2+2;i<=n;i++){
s2-=li[i].r;
s2+=li[i-1].r;
ans=max(ans,base+s2-s1);
}
cout<<ans<<'\n';
return ;
}
int main(){
int T;cin>>T;
while(T--){
init();solve();
}
return 0;
}
CF2146E
涉及到 mex 转化的神仙题。
考虑一个弱化是我们求出一个数组中 \(a\) 所有没出现过的 \(x\),\(a_i >x\) 的数量,我们把这个记作 \(b_x\)。在题目的数据范围中 \(0\le x\le n\) 。
我们这个转化不是没有道理的,因为 mex 的单调性,\(\max{b_x}\) 就是答案。
继续观察,对于 \([i,r]\) \(i\in [1,r]\) 这些 \(a\) 的子区间,我们观察到这些子区间的 \(b_x\) 都有个特点就是存在一个 \(k\),使得 \([j,r]\ j\in (k,r]\) 的 \(b_x\) 有意义。 \([i,r]\ i\in[1,j]\) 的 \(b_x\) 无意义,这个 \(k\) 就是最大的 \(a_k=x \ k\in [1,r]\)。
有无意义是 \(b_x\) 的定义给出的,因为这个数组中已经有 \(x\) ,\(b_x\) 当然无意义了。
我说这一些的目的是这种状物会让我想到用扫描线。
具体一点,我们从左到右扫描每个 \(r\in[1,n]\),每加入一个新 \(a_r\),首先要把 \(b_{a_r}\) 给到 \(0\),因为无意义。
之后对于 \(i\in[0,a_r-1]\) 的 \(b_i\) 区间加 \(1\),因为这里面每个数都相当于从 \(r\) 开始往后扫,扫到 \(a_r\) 都是大于他们的,根据定义当然要加一。
这个位置的答案就是 \(\max{b_x}\),\(0\le x\le n\)。
做完了。
code
Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
i64 x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=3e5+5;
int n;
int arr[N];
struct seg{
int l;int r;
int lzt;
int mv;
}t[N<<2];
void b(int p,int l,int r){
t[p].l=l;t[p].r=r;
t[p].mv=t[p].lzt=0;
if(l==r){return ;}
int mid=l+r>>1;
b(ls,l,mid);b(rs,mid+1,r);
t[p].mv=max(t[ls].mv,t[rs].mv);
return ;
}
void pushdown(int p){
if(t[p].lzt==0)return;
int k=t[p].lzt;t[p].lzt=0;
t[ls].lzt+=k;t[ls].mv+=k;
t[rs].lzt+=k;t[rs].mv+=k;
return ;
}
void asi(int p,int pos,int k){
if(t[p].l==t[p].r&&t[p].l==pos){
t[p].mv=0;t[p].lzt=0;
return ;
}
int mid=t[p].l+t[p].r>>1;
pushdown(p);
if(pos<=mid)asi(ls,pos,k);
if(mid<pos) asi(rs,pos,k);
t[p].mv=max(t[ls].mv,t[rs].mv);
return ;
}
void add(int p,int l,int r,int k){
if(l<=t[p].l&&t[p].r<=r){
t[p].lzt+=k;t[p].mv+=k;
return ;
}
int mid=t[p].l+t[p].r>>1;
pushdown(p);
if(l<=mid)add(ls,l,r,k);
if(mid<r) add(rs,l,r,k);
t[p].mv=max(t[ls].mv,t[rs].mv);
return ;
}
int qry(){return t[1].mv;}
void init(){
b(1,0,n);
return;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>arr[i];
}
b(1,0,n);
for(int i=1;i<=n;i++){
asi(1,arr[i],0);
if(arr[i]>0)add(1,0,arr[i]-1,1);
cout<<qry()<<' ';
}
cout<<'\n';
return ;
}
int main(){
int T;cin>>T;
while(T--){
init();
solve();
}
return 0;
}