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
*/
posted on 2022-10-07 20:07  HZOI-曹蓉  阅读(73)  评论(0编辑  收藏  举报