暑假2019培训:Day10提高组测试赛

本文解析了一场暑假培训赛中的三道题目:次长上升子序列、团子大家族及异或粽子,分享了多种解题思路包括暴力解法、树状数组及线段树等高级数据结构的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

今天这场模拟赛,比昨天的那场简单吧,刘润达放水了?在这里插入图片描述……~
暴力分,这次给的特别足,特别优秀,以至于我每题暴力,70,60,60,拿了210分,排在了第10名
在这里插入图片描述


题目顺序

  • 1.次长上升子序列
  • 2.团子大家族
  • 3.异或粽子

次长上升子序列(lis)

【题目描述】

最长上升子序列是一道经典的题目,liu_runda很想在模拟赛中考考这个题目,但是他又不想被选手骂出原题,于是就把原题魔改一下再出出来.
对于一个数列a[1],a[2]…a[n], 我们定义子序列是一系列下标的集合: {x1,x2…xm}
其中, 1<=x1<x2<x3…<xm<=n
本题的上升子序列应满足a[x1]<=a[x2]<=a[x3]…<=a[xm], 也就是说, 我们考虑的是非严格的上升子序列(或者说,不下降子序列)
两个子序列不同, 当且仅当有一个下标被一个子序列包含却不被另一个子序列包含.
给出一个数列, 你需要找出所有非严格的上升子序列中第二长的子序列的长度. 它有可能比最长上升子序列短, 也有可能和最长上升子序列一样长.
本题的每个输入文件包含多组数据.

【输入格式】
每个输入文件包含多组测试数据,第一行一个整数T表示测试数据的组数.
接下来每组数据第一行一个整数n, 表示数列的长度,第二行n个整数表示数列.
【输出格式】
T行, 第i行一个数字表示第i组输入的次长上升子序列长度.
【样例输入】

5
10
10 1 8 10 2 6 4 1 5 4
10
8 2 6 1 6 8 10 3 7 4
10
1 8 7 9 9 6 10 3 2 2
10
5 4 8 2 2 2 2 9 3 3
10
8 4 9 7 6 9 10 3 7 3

【样例输出】

4
4
5
5
4

【数据范围】

100%的数据, T=5, 1<=ai<=100000
第1,2,3个测试点, 2<=n<=10
第4,5,6,7个测试点, 2<=n<=1000
第8,9,10个测试点, 2<=n<=100000

====================================================
这题毛的一看没有什么思路
于是便迅速打了个暴力
答案要么是最长上升子序列的长度, 要么是最长上升子序列的长度减一. 具体来说, 如果从原序列中选出最长上升子序列的方法唯一, 那么答案是最长上升子序列的长度减一, 否则答案是最长上升子序列的长度.
不妨把问题转化为最长上升子序列的方案数是否为1.
思路很简单,就直接上代码了:

#include<bits/stdc++.h>
using namespace std;
long long n,m,k,maxx=0,max_x=0,maxn=0,cnt_;
const int N=1e5+5;
long long a[N],cnt[N];
long long b[N];
int vis[N][3];
long long f[N];
/*int lowbit(int x){return x&(-x);}
void add(int k,int x){while(k<=n){if(x>vis[k][0])vis[k][1]=vis[k][0],vis[k][0]=k;else if(x>vis[k][1])vis[k][1]=x;k+=lowbit(k);}}
int get_sum(int x)
{
	maxx=0;max_x=0;
    while(x)
    {
   
    }
}*/
int main()
{
	freopen("lis.in","r",stdin);
	freopen("lis.out","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--)
    {
    	maxx=0,max_x=0,maxn=0;
    	int i,j,ans,_ans;
    	for(scanf("%lld",&n),i=0;++i<=n&&scanf("%lld",&a[i]);//vis[i][0]=vis[i][1]=0,f[i][0]=1,f[i][1]=0);
	    );
	/*	for(add(a[1],1),i=1;++i<=n;)
	    {
	    	get_sum(a[i]);
	    	f[i][0]=max(f[i][0],maxx+1);
	    	f[i][1]=max(f[i][1],max_x+1);
	    	add(a[i],f[i][0]);
	    	add(a[i],f[i][1]);
		}
		for(i=0;++i<=n;)
		{
			if(f[i][0]>ans)
            {
                _ans=ans;ans=f[i][0];
                if(f[i][1]>_ans)_ans=f[i][1];
            }
            else
            if(f[i][0]>_ans)_ans=f[i][0];
		}*/
		for(i=0;++i<=n;)f[i]=cnt[i]=1;
        for(i=1;++i<=n;)
            for(j=0;++j<i;)
                if(a[j]<=a[i])
                    if(f[j]+1>f[i])f[i]=f[j]+1,cnt[i]=cnt[j];
                    else if(f[j]+1==f[i])++cnt[i];;
        for(i=0;++i<=n;)
            if(f[i]>maxx)max_x=maxx,maxx=f[i],maxn=i;
            else max_x=std::max(max_x,f[i]);
        printf("%d\n",cnt[maxn]>1?maxx:max_x);		
	}
	return 0;
}

没有错,看到这个代码,证明着我还有一个思路,那就是
.
.
.
.
.
.
树状数组~在这里插入图片描述
O(nlogn)的做法稍为复杂.
我们采用树状数组求解最长上升子序列的方法.
首先定义DP状态: f[i][j]表示从前i个数字中选出一个以数值j结尾的上升子序列的最大长度, g[i][j]表示得到f[i][j]的方案数.
f[i][j]的转移方程为:
if(a[i]==j)f[i][j]=max(f[i-1][j], 1 + max(f[i-1][1],f[i-1][2]…f[i-1][j-1]) )
else f[i][j]=f[i-1][j]
分析从f[i-1]这一行到f[i]这一行的转移, 真正需要修改的只是f[i-1][a[i]]变为f[i][a[i]], 取一个max, 而这个求解需要我们对f[i-1]的前缀求一个最大值. 对树状数组稍加改动就可以做到维护.
方案数的统计需要为树状数组维护的最大值加一维附加信息表示得到最大值的方案数.
by yjh yjh博客点击这里

#include<bits/stdc++.h>
using namespace std;

inline int read() {
	int num=0;
	char c=getchar();
	for(;c<'0' || c>'9';c=getchar());
	for(;c>='0' && c<='9';c=getchar()) num=(num<<1)+(num<<3)+c-48;
	return num;
}

inline int lowbit(int x) {
	return x&(-x);
}

int maxa,max1,max2;
int c[110000][2];

inline void add(int x,int s) {
	
	while (x<=maxa) {
		if (s>c[x][0]) c[x][1]=c[x][0],c[x][0]=s;
		else if (s>c[x][1]) c[x][1]=s;
		x+=lowbit(x);
	}
	
}

inline void ask(int x) {
	
	max1=0,max2=0;
	while (x) {
		if (c[x][0]>max1) {
			max2=max1,max1=c[x][0];
			if (c[x][1]>max2) max2=c[x][1];
		}
		else if (c[x][0]>max2) max2=c[x][0];
		x-=lowbit(x);
	}

}

int T,n,a[110000];
int f[110000][2];

int main() {
	
	freopen("lis.in","r",stdin);
	freopen("lis.out","w",stdout);
	
	T=read();
	
	while (T--) {
		
		n=read();maxa=0; 
		for(int i=1;i<=n;i++) a[i]=read(),maxa=max(maxa,a[i]);
		add(a[1],1);
//		cout<<1<<endl;
//		for(int j=1;j<=maxa;j++) cout<<c[j][0]<<' '<<c[j][1]<<' '<<j<<endl;
//			cout<<"--------------"<<endl;
		for(int i=2;i<=n;i++) {
			ask(a[i]);
//			cout<<i<<endl;
			f[i][0]=max(f[i][0],max1+1);
			if (max2 != 0 || max2 != max1) f[i][1]=max(f[i][1],max2+1);
//			cout<<f[i][0]<<' '<<f[i][1]<<' '<<max1<<' '<<max2<<endl;
			add(a[i],f[i][0]);
			add(a[i],f[i][1]);
//			for(int j=1;j<=maxa;j++) cout<<c[j][0]<<' '<<c[j][1]<<' '<<j<<endl;
//			cout<<"--------------"<<endl;
		}
		
		int ans1=0,ans2=0;
		for(int i=1;i<=n;i++) {
			if (f[i][0]>ans1) {
			    ans2=ans1,ans1=f[i][0];
		     	if (f[i][1]>ans2) ans2=f[i][1];
		    }
		    else if (f[i][0]>ans2) ans2=f[i][0];
		}
		for(int i=1;i<=100000;i++) c[i][0]=c[i][1]=0,f[i][0]=1,f[i][1]=0;
		printf("%d\n",ans2);
	}
	
	
	return 0;
}

这题思路是真的 很多的
花样百出的,线段树版本:by yjz yjz博客点击这里

#include<bits/stdc++.h>
using namespace std;
const int Max_N=1e5;
int T,N,Max=0,B[Max_N+10]={};
unsigned long long Cnt=0;
struct kk{
	int l,r,Val;
	unsigned long long Cnt;
} A[Max_N*4+10]={};
inline void Build(int x,int l,int r){
	A[x].l=l; A[x].r=r; A[x].Val=A[x].Cnt=0;
	if(l==r) return;
	Build(x<<1,l,(l+r)>>1);
	Build((x<<1)+1,((l+r)>>1)+1,r);
}
inline void Add(int x,int k,int Val,long long Num){
	if(A[x].l==A[x].r){
		if(Val==A[x].Val) A[x].Cnt+=Num;
		if(Val>A[x].Val) A[x].Val=Val,A[x].Cnt=Num;
		return;
	}
	if(k<=A[x<<1].r) Add(x<<1,k,Val,Num);
	else Add((x<<1)+1,k,Val,Num);
	A[x].Val=max(A[x<<1].Val,A[(x<<1)+1].Val); A[x].Cnt=0;
	if(A[x<<1].Val==A[x].Val) A[x].Cnt+=A[x<<1].Cnt;
	if(A[(x<<1)+1].Val==A[x].Val) A[x].Cnt+=A[(x<<1)+1].Cnt;
}
inline pair<int,long long> Ask(int x,int l,int r){
	if(l<=A[x].l&&A[x].r<=r) return make_pair(A[x].Val,A[x].Cnt);
	pair<int,int> Ans1=make_pair(-1,-1),Ans2=make_pair(-1,-1);
	if(l<=(A[x].l+A[x].r)>>1) Ans1=Ask(x<<1,l,r);
	if(r>(A[x].l+A[x].r)>>1) Ans2=Ask((x<<1)+1,l,r);
	if(Ans1.first==-1) return Ans2;
	if(Ans2.first==-1) return Ans1;
	return Ans1.first==Ans2.first?make_pair(Ans1.first,Ans1.second+Ans2.second):(Ans1.first>Ans2.first?Ans1:Ans2);
}
int main(){
	freopen("lis.in","r",stdin);
	freopen("lis.out","w",stdout);
	scanf("%d",&T);
	for(;T--;){
		scanf("%d",&N);
		Max=0;
		for(int i=1;i<=N;i++) scanf("%d",&B[i]),Max=max(Max,B[i]);
		Build(1,0,Max); Add(1,0,0,1);
		Max=Cnt=0;
		for(int i=1;i<=N;i++){
			pair<int,long long> Ans=Ask(1,0,B[i]);
			if(Ans.first+1==Max) Cnt+=Ans.second;
			if(Ans.first+1>Max) Max=Ans.first+1,Cnt=Ans.second;
			Add(1,B[i],Ans.first+1,Ans.second>2?2:Ans.second);
		}
		if(Cnt>1) printf("%d\n",Max);
		else printf("%d\n",Max-1);
	}
	return 0;
}

团子大家族(clannad)

【题目描述】

dango, dango, dango daikazoku
团子, 团子, 团子大家族。
团子(だんご)是 动画《Clannad》中虚构的一种很萌的生物,也是本题的主人公。
团子们在一起做游戏。首先,n个团子们每个团子有一个编号,排成一排。
接下来进行若干轮操作。
奇数轮,每个团子把自己的编号变成自己原先编号和右边相邻的团子原先编号的较大值,同时最右边的团子离开游戏。
偶数轮,每个团子把自己的编号变成自己原先编号和左边相邻的团子原先编号的较小值,同时最左边的团子离开游戏。
每过一轮都会离开一个团子,经过(n-1)轮之后恰好还有一个团子。团子们想知道最后剩下的这个团子身上的编号是多少。

【输入格式】

第1行一个整数n, 表示团子的个数
第2行n个整数, 空格隔开, 从左到右表示每个团子的编号a1,a2…ai…an

【输出格式】

一行一个整数表示最后剩下的团子的编号.

【样例输入1】

7
70611 29202 67893 80203 157086 37318 141366

【样例输出1】

80203

【数据范围】

第1,2,3个测试点, 10<=n<=1000
第4,5个测试点, 0<=ai<=1
第6个测试点, 编号的序列单调不下降
全部测试点, 10<=n<=200000, 0<=ai<=200000

首先可以发现, 具体删掉最左边的还是删掉最右边的并没有什么影响, 只要把每相邻的两个数字取min/max变成一个就可以得到O(n2)的做法.

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
long long n,m,k,maxx,minn,x,y,z;
long long a[N];
struct ss
{
	long long l,r,dat,sum,maxx,lazy;
}t[N*4];
inline int _max(int xx,int yy)
{
	return xx>yy?xx:yy;
}
inline int _min(int xx,int yy)
{
	return xx<yy?xx:yy;
}
inline void bl()
{
	int max_x,i,j;int l=1,r=n;
	for(i=0;++i<n;)
	{
		if(i&1)for(--r,j=l;j<r;++j)a[j]=_max(a[j],a[j+1]);
		else for(++l,j=r;j>l;--j)a[j]=_min(a[j],a[j-1]);
	}
	printf("%lld",a[(n+1)>>1]);
	return;
}
int main()
{
	freopen("clannad.in","r",stdin);
	freopen("clannad.out","w",stdout);
	int i,j;
	for(scanf("%lld",&n),i=0;++i<=n&&scanf("%lld",&a[i]););
	
    if(n<=5000){bl();return 0;}
	if(n&1)printf("%lld",a[n+1>>1]);
	else printf("%lld",_max(a[n>>1],a[n>>1|1]));
	
	return 0;
}

这题其实OJ犯了一个十分玄学的错误,把我90分的代码编译 成了60分
在这里插入图片描述
如过你好奇问暴力不是只有50分吗~?
不,
看我这段
在这里插入图片描述
它并不是骗分,是有依据的(虽然我第一种情况打错了)
当n为偶数时最终结果只和a[n/2]和a[n/2+1]有关, 答案为max(a[n/2], a[n/2 + 1], 当n为奇数时最终结果只和a[n/2],a[n/2+1], a[n/2+2]有关, 答案为min(max(a[n/2],a[n/2 + 1]), max(a[n/2 + 1], a[n/2 + 2]) ), 得到一个非常简洁的解法
所以代码应该为

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
long long n,m,k,maxx,minn,x,y,z;
long long a[N];
struct ss
{
   long long l,r,dat,sum,maxx,lazy;
}t[N*4];
inline int _max(int xx,int yy)
{
   return xx>yy?xx:yy;
}
inline int _min(int xx,int yy)
{
   return xx<yy?xx:yy;
}
int main()
{
   freopen("clannad.in","r",stdin);
   freopen("clannad.out","w",stdout);
   int i,j;
   for(scanf("%lld",&n),i=0;++i<=n&&scanf("%lld",&a[i]););
   if(n&1)printf("%lld\n",_min(max(a[n>>1],a[n/2+1]),_max(a[n/2+1],a[n/2+2])));
   else printf("%lld",_max(a[n>>1],a[n/2+1]));
   
   return 0;
}

这题其实也是一道二分答案, 把小于x的数看作0, 大于等于x的数看作1, 转化成一个01序列的问题.
仔细观察01序列在两轮操作后的变化, 会发现, 除了单独的0在一轮取max操作后消失, 单独的一个1在一轮取min操作后消失, 大部分数字在两轮操作后完全不变.
首先我们暴力做前两轮操作, 接下来就没有单独的0了.
我们考虑连续的一段0/1, 两轮操作后它的长度很可能不变. 除非是在整个序列的最边上, 两轮操作后它的长度会减小1. 实际上, 在暴力做完前两轮操作后, 再连续做两轮操作相当于把序列最左和最右的数字各删掉一个. 于是容易得到O(nlogn)的算法
有上面的数学归纳,谁还会去想打个二分答案~?
在这里插入图片描述


异或粽子(xor)

【题目描述】

因为考前一天发现T3是去年D班考过的题, 所以liu_runda决定临时换题, 他找到了十二省联考的包, 发现day1t1搬过来很合适, 就开始抄题面.
小粽是一个喜欢吃粽子的好孩子。今天她在家里自己做起了粽子。
小粽面前有n 种互不相同的粽子馅儿,小粽将它们摆放为了一排,并从左至右编号为1 到n。第i 种馅儿具有一个非负整数的属性值ai。每种馅儿的数量都足够多,即小粽不会因为缺少原料而做不出想要的粽子。小粽准备用这些馅儿来做出k 个粽子。
小粽的做法是:选两个整数数l, r,满足1 ≤ l ≤ r ≤ n,将编号在[l, r] 范围内的所有馅儿混合做成一个粽子,所得的粽子的美味度为这些粽子的属性值的异或和。(异或就是我们常说的xor 运算,即C/C++ 中的 ˆ 运算符)
小粽想品尝不同口味的粽子,因此它不希望用同样的馅儿的集合做出一个以上的粽子。小粽希望她做出的所有粽子的美味度之和最大。请你帮她求出这个值吧!

【输入格式】

第一行两个正整数n, k,表示馅儿的数量,以及小粽打算做出的粽子的数量。
接下来一行为n 个非负整数,第i 个数为ai,表示第i 个粽子的属性值。
对于所有的输入数据都满足:1 ≤ n ≤ 5 × 105, 1 ≤ k ≤ min{n(n−1)/2 , 2 × 105}
, 0 ≤ ai ≤4294967295。

【输出格式】

输出一行一个整数,表示小粽可以做出的粽子的美味度之和的最大值。

【样例输入1】

3 2
1 2 3

【样例输出1】
6
【样例1解释】

选[3,3]和[1,2]两个区间, 两个粽子的美味度均为3, 总和为6.

【数据范围】

测试点1,2,3,4,5,6,7,8, n<=103,k<=103(40%)
测试点9,10,11,12, n<=5105,k<=103(20%)
测试点13,14,15,16, n<=103,k<=2
105(20%)
测试点17,18,19,20, n<=5105,k<=2105 (20%)

============================================================
这题可真的直接,直接告诉了我们这道题是哪里来的了
也能通融,毕竟第三题本来不是这道,是去年刘润达在D班出的题目,他想不到,去年去D班的现在来了C班》》?(装弱??虐菜??)
在这里插入图片描述
在这里插入图片描述
这是今年的一道省选题.
题意是给出一个长度为n的数列, 找出异或和前k大的区间, 并求这些区间的异或和的代数和. 省选出题人对它的描述是”一道快乐的送分题”
n2枚举排序再取前k大就有40分.
类似超级钢琴的堆, 借助可持久化trie进行区间异或最大值的求解.
那我60分,加了一个夺命性剪枝,想不到保了命啊~

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
const int N=5e5+5;
long long sum[N];
long long a[N],maxx[N],f[N*100];
inline int _max(int xx,int yy)
{
	return xx>yy?xx:yy;
}
void dfs()
{
	int i,j,len=0;
    for(i=0;++i<=n;)
      for(j=i-1;++j<=n;)
        f[++len]=sum[j]^sum[i-1];
    long long ans=0;
    for(sort(f+1,f+len+1),i=len;i>=len-k+1;--i)ans+=f[i];
    printf("%lld",ans);
    return;
}
int main()
{
	freopen("xor.in","r",stdin);
	freopen("xor.out","w",stdout);
	scanf("%d%d",&n,&k);
	int i,j,len;sum[0]=0;
	for(i=0;++i<=n&&scanf("%d",&a[i]);sum[i]=sum[i-1]^a[i]);
	if(n<=1000)dfs();
	else
	{
	    int i,j,len=0;
        for(i=0;++i<=n;)
            for(j=i;j<=n;j>>=1)
                f[++len]=sum[j]^sum[i-1];
        long long ans=0;
        for(sort(f+1,f+len+1),i=len;i>=len-k+1;--i)ans+=f[i];
        printf("%lld",ans);
    }
 //   if(n<=1e3&&m>=1e3)dfsiii();
	
	
	return 0;
}

正解代码,稍安勿躁~
by std

#include<cstdlib>
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 500005
int N,K,cnt,root[MAXN],_size[MAXN*50], chi[MAXN*50][2], _id[MAXN*50];
unsigned int A[MAXN],A_now,B[MAXN];
void _read(unsigned int &x){
    x = 0;
    char ch = getchar();
    while(ch<'0' || ch >'9'){ch = getchar();}
    while(ch>='0' && ch<='9'){
        x = x*10 + ch -'0';
        ch = getchar();
    }
    return ;
}
bool cmp(unsigned int x, unsigned int y){
    return x>y;
}
void update(int old,int &now,unsigned int id,int x){

    now = ++cnt;
    int tmp = now;
    int tmp_old = old;
    for(int i=31;i>=0;i--){
        _size[tmp] = _size[tmp_old] + x;
        int c = ((id>>i)&1);
        chi[tmp][1-c] = chi[tmp_old][1-c];
        chi[tmp][c] = ++cnt;
        tmp = chi[tmp][c];
        tmp_old = chi[tmp_old][c];
    }
    _size[tmp] = _size[tmp_old] + x;
    
    return ;
}
unsigned int query(int now, unsigned int target){
    int tmp = now;
    
    unsigned int ans = 0;
    for(int i=31;i>=0;i--){
        
        int c = (((target>>i)&1)^1);
        if(_size[chi[tmp][c]] > 0){
            ans = (ans<<1)+1;
        }
        else {
            c^=1;
            ans = (ans<<1)+0;
        }
        tmp = chi[tmp][c];
        
        
    }
    
    return ans;
}
struct my_data {
    int j;
    unsigned int val;
    my_data(int j, unsigned int val) : j(j), val(val) {}
    friend bool operator < (my_data a, my_data b)    
    {
        return a.val < b.val;
    }
};
priority_queue<my_data>pq;
int main(){
    freopen("xor.in","r",stdin);
    freopen("xor.out","w",stdout);
    scanf("%d%d",&N,&K);
    for(int i=1;i<=N;i++){
        _read(A[i]);
        //scanf("%d",&A[i]);
    }
    
    root[0] = 0;
    update(root[0],root[1],0,+1);
    A_now = 0;
    for(int i=1;i<=N;i++){
        
        A_now^=A[i];
        B[i] = A_now;
        unsigned int max_val = query(root[i], B[i]);
        //cout<<"guugug  "<<max_val<<endl;
        pq.push(my_data( i , max_val));
        update(root[i],root[i+1],B[i],+1);
    }
    
    long long ans = 0;
    for(int i=1;i<=K;i++){
        my_data tmp = pq.top();
        ans = ans + tmp.val;
        //cout<<tmp.val<<endl;
        pq.pop();
        update(root[tmp.j],root[tmp.j],tmp.val^B[tmp.j],-1);
        unsigned int max_val = query(root[tmp.j], B[tmp.j]);
        pq.push(my_data( tmp.j , max_val));
    }
    printf("%lld\n",  ans);
    fclose(stdin);fclose(stdout);
    return 0;
}

在这里插入图片描述

结束~~

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值