CSP模拟17 最大匹配 挑战ABC 三级跳 经典线性基
T1【贪心+结论证明】给出2n个二元组[a,b],求max(|ai-bi|,|ai-bj|,|aj-ai|,|aj-bj|)。(n<=1e5)
发现按照a>b排列二元组[a,b],那么点对贡献就是对角线差值,都转化成同向差值就是sum升序排序。可以证明这样的排列选择下一定是回文型构造最优。
点击查看代码
//吾必胜
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define ll long long
#define chu printf
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')h=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*h;
}
const int N=1e5+100;
struct Bag
{
int a,b;
}pai[N<<1];
int n;
inline bool Cmp(Bag A,Bag B)
{
return (A.a+A.b)>(B.a+B.b);
}
int main()
{
// freopen("","r",stdin);
// freopen("","w",stdout);
n=re();
n<<=1;
_f(i,1,n)
{
int x=re(),y=re();
pai[i].a=max(x,y);
pai[i].b=min(x,y);
}
sort(pai+1,pai+1+n,Cmp);
n>>=1;ll sum=0;
_f(i,1,n)
{
sum+=(ll)pai[i].a-(ll)pai[n*2-i+1].b;
}
chu("%lld",sum);
return 0;
}
/*
3
100 1
95 7
90 8
80 10
70 11
60 20
*/
T2[构造+结论]给出ABC串,求最少的[l,r,cor]操作次数,表示把[l,r]染色成一种颜色cor,使得ABC都有n个(初始长度n*3)(n<=1e5)
2次操作一定可以:找到最短前缀使得某字符X出现n次,(最小保证其他字符出现<n次),剩下的按照需求分配就可以。
1次:如果有2个mx,一个mi,可以找到一个区间[l,r]使得mx1出现mx1_cnt-n次,mx2出现mx2_cnt-n次,那么这一段变成mi刚好,双指针维护。
点击查看代码
//吾必胜
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define ll long long
#define chu printf
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')h=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*h;
}
const int N=3e5+100;
int n,sd,cnt[3],buc[3];
char s[N*3];
inline bool Find_1step()
{
int top=0;
int cm1,cm2,cor;
if(cnt[0]<sd)cor=0,cm1=1,cm2=2;
else if(cnt[1]<sd)cor=1,cm1=0,cm2=2;
else cor=2,cm1=0,cm2=1;
// chu("%d %d %d\n",cnt[0],cnt[1],cnt[2]);
cnt[cm1]-=sd;
cnt[cm2]-=sd;//需要多少个
//chu("cor:%c cm1:%c cm2:%c\n",cor+'A',cm1+'A',cm2+'A');
// chu("%d %d\n",cnt[cm1],cnt[cm2]);
_f(i,1,n)
{
if(i>1&&top>=i)buc[s[i-1]-'A']--;
if(top<i)top=i-1,buc[0]=buc[1]=buc[2]=0;
while((top+1)<=n&&buc[cm1]+(s[top+1]==cm1+'A')<=cnt[cm1]&&buc[cm2]+(s[top+1]==cm2+'A')<=cnt[cm2])//窗口
++top,buc[s[top]-'A']++;
// chu("now %d--%d:%d %d %d\n",i,top,buc[0],buc[1],buc[2]);
if(buc[cm1]==cnt[cm1]&&buc[cm2]==cnt[cm2])//刚好有合法的区间
{
chu("1\n");
chu("%d %d %c",i,top,cor+'A');return 1;
}
}
return 0;
}
inline void Find2_step()
{
int mx,ci1,ci2;//找个大的就得了
// if(cnt[0]>sd)mx=0,ci1=1,ci2=2;
// else if(cnt[1]>sd)mx=1,ci1=0,ci2=2;
// else mx=2,ci1=0,ci2=1;
// _f(i,1,n)
// {
// buc[s[i]-'A']++;
// if(buc[mx]==sd)
// {
// if(i==n)//不合法的
// {
// if(cnt[0]>sd&&mx!=0)mx=0,ci1=1,ci2=2;
// else if(cnt[1]>sd&&mx!=1)mx=1,ci1=0,ci2=2;
// else if(cnt[2]>sd&&mx!=2)mx=2,ci1=0,ci2=1;//换一个
// }
// break;
// }
// }
//chu("mx:%d\n",mx);
_f(i,1,n)
{
buc[s[i]-'A']++;
if(buc[0]==sd)
{
mx=0,ci1=1,ci2=2;break;
}
if(buc[1]==sd)
{
mx=1,ci1=0,ci2=2;break;
}
if(buc[2]==sd)
{
mx=2,ci1=1,ci2=0;break;
}
}
buc[0]=buc[1]=buc[2]=0;
_f(i,1,n)
{
buc[s[i]-'A']++;
if(buc[mx]==sd)//够了
{
buc[ci1]=sd-buc[ci1];
buc[ci2]=sd-buc[ci2];
chu("2\n");
chu("%d %d %c\n",i+1,(i+1)+buc[ci1]-1,ci1+'A');
chu("%d %d %c\n",i+1+buc[ci1],n,ci2+'A');
return;
}
}
}
int main()
{
// freopen("1.in","r",stdin);
//freopen("","w",stdout);
n=re();sd=n;
scanf("%s",s+1);
n=n*3;//长度
_f(i,1,n)
++cnt[s[i]-'A'];
if(cnt[0]==cnt[1]&&cnt[1]==cnt[2])
{
chu("0");return 0;
}
//int mx1=max{buca,bucb,bucc};
//int mx3=min{buca,bucb,bucc};
int cntt=(cnt[0]>=sd)+(cnt[1]>=sd)+(cnt[2]>=sd);
if(cntt==2)//有2个大的才会有1的可能
{
if(Find_1step())return 0;
}
Find2_step();
return 0;
}
/*
3
ABABCABAB
4
BCBCCBBBBCBB
5
BAABBBABCCABAAB
*/
T3【数据结构维护离线统计序列答案+贪心+结论】给出一个序列,每个位置有值ai,每次询问[l,r],求一个三元组[a,b,c]满足l<=a<b<c<=r,c-b>=b-a,而且val[a]+val[b]+val[c]最大,多组询问。(n,q<=5e5)
首先猜想结论,我们固定[a,b],那么对于最优解,val[a],val[b]一定是[a,b]区间的最大值,否则一定可以缩小区间使得答案不更劣。所以对于固定mid,它可以有贡献的二元组[a,b]就是mid向右第一个>=val[mid]和向左第一个>val[mid]的2对,直接单调队列求出这n个答案。
考虑统计答案,很经典的套路,对于一段区间[l,r],我们固定c的位置,那么有贡献的二元组[a,b]满足c-b>=b-a,2b-a<=c,也就是说,(a,b)可以对一段从2b-a~n的决策点产生贡献,在每个点的贡献就是val[a]+val[b] +val[c],我们按照l排序,每次询问先加入l合法的二元组[a,b]然后统计贡献。线段树维护区间max。
点击查看代码
//慎独,深思,毋躁,自律,专注
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define ll long long
#define rint register int
#define chu printf
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')h=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*h;
}
const int N=5e5+100;
ll mx[N<<2],tag[N<<2],val[N<<2];
int a[N],n;
deque<int>s;
vector<pair<int,int> >que[N];//下标升序[l],第一维r,第二维id
ll ans[N];
vector<int>g[N];//记录每个合法点对,下标小,内部大
#define lson (rt<<1)
#define rson (rt<<1|1)
inline void Build(int rt,int l,int r)
{
if(l==r)
{
mx[rt]=a[l];
val[rt]=a[l];//chu("val[%d]:%d\n",rt,val[rt]);
return;//下标意义
}
int mid=(l+r)>>1;
Build(lson,l,mid);Build(rson,mid+1,r);
val[rt]=max(val[lson],val[rson]);
mx[rt]=val[rt];
}
inline void Pushup(int rt)
{
mx[rt]=max(mx[lson],mx[rson]);
}
inline void Pushdown(int rt)
{
if(!tag[rt])return;
tag[lson]=max(tag[lson],tag[rt]);
tag[rson]=max(tag[rson],tag[rt]);
mx[lson]=max(mx[lson],tag[rt]+val[lson]);
mx[rson]=max(mx[rson],tag[rt]+val[rson]);
}
inline void Update(int rt,int l,int r,int L,int R,ll ad)
{
if(L<=l&&r<=R)
{
tag[rt]=max(tag[rt],ad);
mx[rt]=max(mx[rt],val[rt]+ad);
// chu("val[%d]:%d mx[%d]:%lld\n",rt,val[rt],rt,mx[rt]);
return;
}
Pushdown(rt);
int mid=(l+r)>>1;
if(L<=mid)Update(lson,l,mid,L,R,ad);
if(R>mid)Update(rson,mid+1,r,L,R,ad);
Pushup(rt);
}
inline ll Query(int rt,int l,int r,int L,int R)
{
if(L<=l&&r<=R)
return mx[rt];
Pushdown(rt);
int mid=(l+r)>>1;
ll ans=0;
if(L<=mid)ans=Query(lson,l,mid,L,R);
if(R>mid)ans=max(ans,Query(rson,mid+1,r,L,R));
return ans;
}
int main()
{
// freopen("1.in","r",stdin);
// freopen("","w",stdout);
n=re();
_f(i,1,n)
{
a[i]=re();
while(!s.empty()&&a[s.back()]<a[i])//单调递减,严格
{
g[s.back()].push_back(i);
s.pop_back();
}
if(!s.empty())g[s.back()].push_back(i);//对于i,s.back满足>=i,严格和不严格,去重复作用
s.push_back(i);
}
// _f(i,1,n)
// {
// for(rint to:g[i])
// chu("pair %d %d\n",i,to);
// }
int Q=re();
_f(i,1,Q)
{
int l=re(),r=re();
que[l].push_back(pair<int,int>(r,i));
}
Build(1,1,n);
f_(i,n,1)
{
for(rint ele:g[i])//加入合法点对
{
if(2*ele-i<=n)
{
//chu("update:%d--%d:%d\n",2*ele-i,n,a[i]+a[ele]);
Update(1,1,n,2*ele-i,n,a[i]+a[ele]);
}
}
for(pair<int,int>r:que[i])//右边界,序号
{
// chu("query:%d--%d:%lld\n",i,r.first,Query(1,1,n,i,r.first));
ans[r.second]=Query(1,1,n,i,r.first);
}
}
_f(i,1,Q)chu("%lld\n",ans[i]);
return 0;
}
/*
5
5 2 1 5 3
3
1 4
2 5
1 5
*/
\(O(q*logn+n*logn)\)
T4【线性基】求[l,r]范围内的质数能凑出来的数个数。(r<=1e12)
虽然正解WA了,但是思想是可以借鉴的。
【1】对于一段长区间的二进制分解,不重不漏:
for(int i=0;i<=62;++i) { if(l&(1ll<<i)&&l+(1ll<<i)-1<=r){Divid(l,l+(1ll<<i+1));l+=(1ll<<i);} }
for(int i=62;i>=0;--i) { if(l+(1ll<<i)-1<=r) {Divid(l,l+(1ll<<i)-1);l+=(1ll<<i);} }
注意正要求&(1<<i)==1,反着不要求
【2】长度范围小根号下范围筛质数
【3】极长区间蒙一个所有都能凑出
点击查看代码
//慎独,深思,毋躁,自律,专注
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define ll long long
#define chu printf
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')h=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*h;
}
#define int ll
const int N=1e6+100;
bool vis[N];
int prime[N],tot,cnt;
ll p[67],p2[67];//线性基
int T;
inline void Ins(ll x)
{
f_(i,62,0)
{
if(!(x&(1ll<<i)))continue;
if(!p[i]){p[i]=x;++cnt;return;}
x^=p[i];
}
}
inline void Divid(ll l,ll r,int len)
{
if(len>=17)
{
_f(i,1,len)Ins(1ll<<i);return;
}
//chu("divid;%lld--%lld:%d\n",l,r,len);
ll bj=sqrt(r);
fill(vis,vis+(r-l)+100,0);//查找在1~bj之间的质数,筛去区间内因子
for(rint i=1;i<=tot&&(ll)prime[i]<=bj;++i)
{
ll bas=l/prime[i];
if(bas==1||bas*prime[i]<l)++bas;
for(ll j=bas;j*prime[i]<=r;++j)//循环是这个质数的多少倍
vis[j*prime[i]-l+1]=1;//可以被表示,不是质数
}
for(ll i=l;i<=r;++i)
{
//chu("vis[%lld]:%d\n",i,vis[i-l+1]);
if(!vis[i-l+1])Ins(i);
}
}
signed main()
{
//freopen("sample-01.in","r",stdin);
//freopen("1.out","w",stdout);
T=re();
p2[0]=1;
_f(i,1,62)p2[i]=p2[i-1]*2ll;
_f(i,2,1000000)
{
if(!vis[i])prime[++tot]=i;
_f(j,1,tot)
{
if(prime[j]*i>1000000)break;
int bs=prime[j]*i;
vis[bs]=1;
if(i%prime[j]==0)break;
}
}
memset(vis,0,sizeof(vis));
while(T--)
{
ll let=re(),ret=re();cnt=0;
memset(p,0,sizeof(p));
ll now=let;//ret++;
_f(i,0,62)
{
if((now&(1ll<<i))&&now+(1ll<<i)-1<=ret)Divid(now,now+(1ll<<i)-1,i),now+=(1ll<<i);
}
// chu("now:%lld\n",now);
f_(i,62,0)
{
if(now+(1ll<<i)-1<=ret)Divid(now,now+(1ll<<i)-1,i),now+=(1ll<<i);
}
chu("%lld\n",p2[cnt]);
}
return 0;
}
/*
3
2 10
999999940 1000000000
2 1000000000000
1
999999940 1000000000
1
205286014976 205302792191
*/