20190724杭电多校第二场

没有补题。。倒是又想到了1002的二分做法,比原来好写了不少,也快了不少。

#include
using namespace std;
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
const int maxn=3e5+5;
int n,a[maxn],dp[maxn],pre[2][maxn],nex[2][maxn]; 
int LIS[maxn];
struct node{
	int val,pos;
	node(int _v=0,int _p=0):val(_v),pos(_p){}
};
vector v[maxn];//第一维下标为LIS长度 
int main(){
//	freopen("in.txt","r",stdin);
	while(~scanf("%d",&n)){
		For(i,1,n) scanf("%d",&a[i]);
		
		int len=1,p,l,r,mid,sz,now;//第一遍LIS 
		dp[len]=a[1],pre[0][1]=pre[1][1]=-1,LIS[1]=1;
		v[1].p_b(node(a[1],1));
		For(i,2,n){
			p=lower_bound(dp+1,dp+1+len,a[i])-dp;
			if(p>len) len++;
			dp[p]=a[i];
			v[p].p_b(node(a[i],i));
			LIS[i]=p;
			if(p==1) pre[0][i]=pre[1][i]=-1;
			else{
				sz=v[p-1].size();
				pre[1][i]=v[p-1][sz-1].pos;
				l=0,r=sz-1;
				while(l<=r){
					mid=(l+r)>>1;
					if(v[p-1][mid].val>=a[i]) l=mid+1;
					else now=v[p-1][mid].pos,r=mid-1;
				}
				pre[0][i]=now;
			}
		}
		For(i,1,len) v[i].clear();
		
		len=1;//第二遍LIS 
		dp[len]=a[n],nex[0][n]=nex[1][n]=-1;
		v[1].p_b(node(a[n],n));
		for(int i=n-1;i>=1;i--){
			p=lower_bound(dp+1,dp+1+len,a[i])-dp;
			if(p>len) len++;
			dp[p]=a[i];
			v[p].p_b(node(a[i],i));
			LIS[i]+=p-1;
			if(p==1) nex[0][i]=nex[1][i]=-1;
			else{
				sz=v[p-1].size();
				nex[0][i]=v[p-1][sz-1].pos;
				l=0,r=sz-1;
				while(l<=r){
					mid=(l+r)>>1;
					if(v[p-1][mid].val>=a[i]) l=mid+1;
					else now=v[p-1][mid].pos,r=mid-1;
				}
				nex[1][i]=now;
			}
		}
		For(i,1,len) v[i].clear();
		
		int maxx=0,maxp[2];//得到最左和最右的折点位置 
		For(i,1,n){
			if(LIS[i]>maxx) maxx=LIS[i],maxp[0]=maxp[1]=i;
			else if(LIS[i]==maxx) maxp[1]=i;
		}
		
		for(int k=0;k<2;k++){//统计答案 
			int i=maxp[k],j=nex[k][maxp[k]],cnt=0;
			while(i!=-1){
				cnt++;
				a[cnt]=i;
				i=pre[k][i];
			}
			while(j!=-1){
				cnt++;
				a[cnt]=j;
				j=nex[k][j];
			}
			sort(a+1,a+cnt+1);
			For(i,1,cnt) cout<

1001 Another Chess Problem(待补)

 

1002 Beauty Of Unimodal Sequence(补题 By jlz)

题意:求最长的先升后降子序列中下标字典序最小的和下标字典序最大的。

要求最长的先升后降子序列,可以先正反各求一遍最长上升子序列,枚举每一个点作为转折点,找出最大的即可。

求最长上升子序列有多种方法,这里我采用的是线段树。

对于数列A,每个数的初始位置为id,用结构体存下,将之按值从小到大排序。线段树维护以每个位置结尾的最长上升子序列长度,初始为0。遍历排序后的结构体数组,通过线段树找出【1,id-1】的最大值,则该位置的LIS长度为 这个最大值+1。(因为可能有多个相等的值,此处有两种处理方法,一种是排序的时候,当值相等的时候,id大的排在前面;另一种是先查询完所有相等的值,再插入。)

这样已经可以得到最长的先升后降子序列长度以及所有的转折点。

至于求字典序最大和最小的序列,可以直接贪心,若某个点在答案序列中,那么对于字典序小一定从越前面的转移过来越好,对于字典序大的一定从越后面转移过来越好。

20190724杭电多校第二场_第1张图片

因此在求最长上升子序列的过程中,我们除了要知道最大值之外,还要知道处于最左端/最右端的最大值的位置,这个同样可以通过线段树查询。

我的线段树写的很挫。。没有整理过很好的模板,都是直接YY,想到咋写就咋写,查询位置的那部分不知道能不能写得更优,感觉自己写的是个剪枝的log^2。。。最后2792ms,跑的倒数第4快。。。。。

AC代码:

#include
using namespace std;
#define ll long long
#define db double
#define m_p make_pair
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define mst(a,b) memset(a,b,sizeof(a))
const int maxn=3e5+5;
const db eps=1e-8;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const int seed=131;
int n,a[maxn];
struct node{
	int num,id;
	bool operator<(const node &b)const{
		return numb.id;
	}
}b[maxn];
struct Qnode{
	int l,r,s;
}Q[maxn<<2];
void build(int rt,int l,int r){
	Q[rt].l=l,Q[rt].r=r,Q[rt].s=0;
	if(Q[rt].l==Q[rt].r) return;
	int mid=(Q[rt].l+Q[rt].r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r); 
}
inline void pushup(int rt){
	Q[rt].s=max(Q[ls].s,Q[rs].s);
} 
void insert(int rt,int p,int x){
	if(Q[rt].l==Q[rt].r){
		Q[rt].s=x;return;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(p<=mid) insert(ls,p,x);
	else insert(rs,p,x);
	pushup(rt);
}
int dp[4][maxn],LIS[2][maxn];
int query(int rt,int l,int r){//求区间最大值 
	if(l>r) return 0;
	if(Q[rt].r==r&&Q[rt].l==l||Q[rt].l==Q[rt].r){
		return Q[rt].s;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(r<=mid) return query(ls,l,r);
	else return max(query(ls,l,mid),query(rs,mid+1,r));
}
int query2(int rt,int x){//left
	if(Q[rt].l==Q[rt].r){
		return Q[rt].l;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(Q[ls].s>=x) return query2(ls,x);
	else return query2(rs,x);
}
int query1(int rt,int r,int x){//right
	if(Q[rt].l==Q[rt].r){
		return Q[rt].l;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(r<=mid||Q[rs].s=Q[rt].r&&Q[rs].s>=x) return query1(rs,r,x);
	else if(query(rs,mid+1,r)>=x) return query1(rs,r,x);
	else return query1(ls,r,x);
}
int ans[2][maxn],cnt[2];
int main(){
//	freopen("in.txt","r",stdin);
	while(~scanf("%d",&n)){
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[i].num=a[i],b[i].id=i;
		sort(b+1,b+1+n);
		build(1,1,n);
		for(int i=1;i<=n;i++){
			LIS[0][b[i].id]=query(1,1,b[i].id-1)+1;
			if(LIS[0][b[i].id]==1){
				dp[0][b[i].id]=dp[1][b[i].id]=0;
			}
			else{
				dp[0][b[i].id]=query2(1,LIS[0][b[i].id]-1);
				dp[1][b[i].id]=query1(1,b[i].id-1,LIS[0][b[i].id]-1);
			}
			insert(1,b[i].id,LIS[0][b[i].id]);
		}
		for(int i=1;i<=n;i++) b[i].id=n-b[i].id+1;
		sort(b+1,b+1+n);
		build(1,1,n);
		int p;
		for(int i=1;i<=n;i++){
			p=n-b[i].id+1;
			LIS[1][p]=query(1,1,b[i].id-1)+1;
			if(LIS[1][p]==1) dp[2][p]=dp[3][p]=0;
			else{
				dp[3][p]=query2(1,LIS[1][p]-1);
				dp[2][p]=query1(1,b[i].id-1,LIS[1][p]-1);
			}
			dp[2][p]=n-dp[2][p]+1;
			dp[3][p]=n-dp[3][p]+1;
			insert(1,b[i].id,LIS[1][p]);
		}
		int pre[2],nex[2],maxx=0;
		for(int i=1;i<=n;i++){
			if(LIS[0][i]+LIS[1][i]-1>maxx){
				maxx=LIS[0][i]+LIS[1][i]-1;
				pre[0]=pre[1]=nex[0]=nex[1]=i;
			}
			else if(LIS[0][i]+LIS[1][i]-1==maxx){
				pre[1]=nex[1]=i;
			}
		}
		for(int i=0;i<2;i++){
			cnt[i]=0;;
			while(pre[i]!=0){
				ans[i][cnt[i]++]=pre[i];
				pre[i]=dp[i][pre[i]];
			}
			nex[i]=dp[i+2][nex[i]];
			while(nex[i]!=n+1){
				ans[i][cnt[i]++]=nex[i];
				nex[i]=dp[i+2][nex[i]];
			}
			sort(ans[i],ans[i]+cnt[i]);
			for(int j=0;j

 

UPD:

问了一下大大怎么用线段树快速查询已知值的位置,大大:“不用线段树,你用个map不就行了。”

所以求出最大值后,求最左端和最右端的位置我改成了用set来做,好写了不少。。。

#include
using namespace std;
#define ll long long
#define db double
#define m_p make_pair
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define mst(a,b) memset(a,b,sizeof(a))
const int maxn=3e5+5;
const db eps=1e-8;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const int seed=131;
int n,a[maxn];
struct node{
	int num,id;
	bool operator<(const node &b)const{
		return numb.id;
	}
}b[maxn];
struct Qnode{
	int l,r,s;
}Q[maxn<<2];
void build(int rt,int l,int r){
	Q[rt].l=l,Q[rt].r=r,Q[rt].s=0;
	if(Q[rt].l==Q[rt].r) return;
	int mid=(Q[rt].l+Q[rt].r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r); 
}
inline void pushup(int rt){
	Q[rt].s=max(Q[ls].s,Q[rs].s);
} 
void insert(int rt,int p,int x){
	if(Q[rt].l==Q[rt].r){
		Q[rt].s=x;return;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(p<=mid) insert(ls,p,x);
	else insert(rs,p,x);
	pushup(rt);
}
int dp[4][maxn],LIS[2][maxn];
int query(int rt,int l,int r){//求区间最大值 
	if(l>r) return 0;
	if(Q[rt].r==r&&Q[rt].l==l||Q[rt].l==Q[rt].r){
		return Q[rt].s;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(r<=mid) return query(ls,l,r);
	else return max(query(ls,l,mid),query(rs,mid+1,r));
}
set st[maxn];//维护LIS长度的位置 
int ans[2][maxn],cnt[2];
int main(){
//	freopen("in.txt","r",stdin);
	while(~scanf("%d",&n)){
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[i].num=a[i],b[i].id=i,st[i].clear();
		sort(b+1,b+1+n);
		build(1,1,n);
		for(int i=1;i<=n;i++){
			LIS[0][b[i].id]=query(1,1,b[i].id-1)+1;
			if(LIS[0][b[i].id]==1){
				dp[0][b[i].id]=dp[1][b[i].id]=0;
			}
			else{
				dp[0][b[i].id]= *(st[LIS[0][b[i].id]-1].begin());
				dp[1][b[i].id]= *(--st[LIS[0][b[i].id]-1].lower_bound(b[i].id));
			}
			insert(1,b[i].id,LIS[0][b[i].id]);
			st[LIS[0][b[i].id]].insert(b[i].id);
		}
		for(int i=1;i<=n;i++) b[i].id=n-b[i].id+1,st[i].clear();
		sort(b+1,b+1+n);
		build(1,1,n);
		int p;
		for(int i=1;i<=n;i++){
			p=n-b[i].id+1;
			LIS[1][p]=query(1,1,b[i].id-1)+1;
			if(LIS[1][p]==1) dp[2][p]=dp[3][p]=0;
			else{
				dp[3][p]=*(st[LIS[1][p]-1].begin());
				dp[2][p]=*(--st[LIS[1][p]-1].lower_bound(b[i].id));
			}
			dp[2][p]=n-dp[2][p]+1;
			dp[3][p]=n-dp[3][p]+1;
			insert(1,b[i].id,LIS[1][p]);
			st[LIS[1][p]].insert(b[i].id);
		}
		int pre[2],nex[2],maxx=0;
		for(int i=1;i<=n;i++){
			if(LIS[0][i]+LIS[1][i]-1>maxx){
				maxx=LIS[0][i]+LIS[1][i]-1;
				pre[0]=pre[1]=nex[0]=nex[1]=i;
			}
			else if(LIS[0][i]+LIS[1][i]-1==maxx){
				pre[1]=nex[1]=i;
			}
		}
		for(int i=0;i<2;i++){
			cnt[i]=0;;
			while(pre[i]!=0){
				ans[i][cnt[i]++]=pre[i];
				pre[i]=dp[i][pre[i]];
			}
			nex[i]=dp[i+2][nex[i]];
			while(nex[i]!=n+1){
				ans[i][cnt[i]++]=nex[i];
				nex[i]=dp[i+2][nex[i]];
			}
			sort(ans[i],ans[i]+cnt[i]);
			for(int j=0;j

1003 Coefficient (待补)

 

1004 Double Tree (待补)

 

1005 Everything Is Generated In Equal Probability (Solved By wtw)

待wtw补题解。

 

1006 Fantastic Magic Cube (待补)

 

1007 Game (待补)

 

1008 Harmonious Army (待补)

 

1009 I Love Palindrome String (待补)

 

1010 Just Skip The Problem (Solved By jlz)

签到题。

因为每次只能确定一位,因此需要每一位都询问一次,不同的询问方案数其实就是位数的排列。

因为模数为1e6+3,对于n<1e6+3,可以预处理线性求解;当n>=1e6+3,答案为0。

AC代码:

#include
using namespace std;
const int maxn=1e6+5;
const int mod=1e6+3;
int f[maxn],n;
int main(){
    f[0]=1;
    for(int i=1;i=mod) puts("0");
        else cout<

1011 Keen On Everything But Triangle (Solved By cys/wtw)

次签到题。

考虑对于一个有序数列,可能形成的最大周长的三角形的取值一定是相邻的三个数,而能构造的一个三角形都不能形成的最坏情况形如斐波那契数列,即项数很小。

对于本题,我们知道主席树可以求区间第k大。对于每个区间,先求第一大、第二大、第三大,如果能组成三角形,即为答案,否则求第四大。。。至多只需要求到第44大左右。

因此时间复杂度为O(m*log_{2}n*f)(f<=50)

AC代码:

#include
#define ll long long
#define pb push_back
#define _mp make_pair
#define db double
#define eps 1e-9
#define inf 1e9
using namespace std;
const int maxn=1e5+7;
const int mod=1e9+7;
inline ll read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
struct node
{
    int l,r,sum;
}no[maxn*40];
int root[maxn];
int cnt;
int n,q;
void build(int &x,int l,int r)
{
    x=++cnt;
    no[x].sum=0;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(no[x].l,l,mid);
    build(no[x].r,mid+1,r);
}
void insertt(int &x,int pre,int l,int r,int pl)
{
    x=++cnt;
    no[x]=no[pre];
    no[x].sum++;
    if(l==r)return;
    int mid=(l+r)>>1;
    if(pl<=mid)insertt(no[x].l,no[pre].l,l,mid,pl);
    else insertt(no[x].r,no[pre].r,mid+1,r,pl);
}
int query(int x,int y,int l,int r,int tt)
{
    if(l==r)return l;
    int mid=(l+r)>>1;
    int tmp=no[no[y].l].sum-no[no[x].l].sum;
    if(tt<=tmp)return query(no[x].l,no[y].l,l,mid,tt);
    if(tt>tmp)return query(no[x].r,no[y].r,mid+1,r,tt-tmp);
}
ll a[maxn],b[maxn],kv[maxn];
int main()
{
    while(cin>>n>>q){
        for(int i=1;i<=n;i++){
            a[i]=read();b[i]=a[i];
        }
        cnt=0;
        sort(b+1,b+1+n);
        int tt=unique(b+1,b+1+n)-b;
       // cout<b[tmp1]){
                    flag=1;
                    cout<

1012 Longest Subarray (补题 By jlz)

对于区间覆盖问题我做的题很少,想了挺久的假做法,完全没往这方面想。

几何旋律一下就秒了,qko想做法,wang9897实现,Orz。

具体解法:

线性扫过整个数列,通过线段树维护,当前位置作为右端点时,哪些区间不能作为左端点,不能作为左端点的区间在线段树中就是一条覆盖的线段。

设当前位置为i,值为x,用名为pos的vector存下每个值的所有位置。

当[1,i]中x的个数小于k时,则对之不能作为左端点的区间是[1,i];

否则,不能作为左端点的区间是[pos[x][pos[x].size()-k]+1,i]。

同时,插入线段的时候,还需要删除前一个x的线段。即对于每个相同的值,在线段树中只需要维护一条线段,

最后找到没有被任意一条线段覆盖的最左端的点即可。

AC代码:

#include
using namespace std;
#define ll long long
#define db double
#define m_p make_pair
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define mst(a,b) memset(a,b,sizeof(a))
const int maxn=1e5+5;
const db eps=1e-8;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const int seed=131;
int n,c,k,sz,x;
struct node{
	int l,r,sum,s;
}Q[maxn<<2];
vector pos[maxn];
inline void pushup(int rt){
	if(!Q[rt].s&&Q[rt].l!=Q[rt].r) Q[rt].sum=Q[ls].sum+Q[rs].sum;
	else if(!Q[rt].s&&Q[rt].l==Q[rt].r) Q[rt].sum=1;
	else Q[rt].sum=0;
}
void build(int rt,int l,int r){
	Q[rt].l=l,Q[rt].r=r,Q[rt].s=0;
	if(l==r){
		Q[rt].sum=1;
		return;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	pushup(rt);
}
void update(int rt,int l,int r,int op){
	if(Q[rt].l==l&&Q[rt].r==r){
		if(op==1) Q[rt].s++;
		else Q[rt].s--;
		pushup(rt);
		return ;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(r<=mid) update(ls,l,r,op);
	else if(l>mid) update(rs,l,r,op);
	else update(ls,l,mid,op),update(rs,mid+1,r,op);
	pushup(rt);
}
int query(int rt,int r){
	if(Q[rt].sum==0) return r;
	if(Q[rt].l==Q[rt].r){
		return Q[rt].l;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(r<=mid||Q[ls].sum>0) return query(ls,r);
	else return query(rs,r);
}
int main(){
//	freopen("in.txt","r",stdin);
	while(~scanf("%d %d %d",&n,&c,&k)){
		if(k==1){
			for(int i=1;i<=n;i++) scanf("%d",&x);
			cout<

 

总结:

本场我很快的找到了签到题1010,过题时排在榜单第15。。然而通常如果前期过于舒服,中后期我就会成为演员。

一段时间后cys说1011可做,主席树暴力找第一大、第二大...我说这显然是m*n*logn,wtw也说这复杂度不对。cys小声抗议了几句,被我无视了QAQ。

然后cys看1002,wtw搞1005,同时和我想1011。一段时间后,我提出了O(n*sqrt(n)*log(n))的做法,莫队+set,也没算复杂度,然后成功演了近三个小时。。。(期间队友要求我去搞别的题依然被我无视QAQ)

wtw搞了挺久1005,过了,cys一直在搞1002,他想到了解法,但是不会线段树求LIS,现学一段时间之后放弃。

最后一小时,确定莫队过不了1011,又一起讨论了做法,想了想是不是构造不出m*n*logn的情况,然后发现最坏情况符合斐波那契数列。。。那么复杂度实际上就是m*logn*(斐波那契项数),也可以近似为log级别。

然后wtw采用cys最初的想法,写了20+分钟过了。

最后时间剩下半小时,我想了个1012的假做法,也没写完。。。

这场比较大的问题在我:没有听从队友的建议想其他题;没有给予自闭1002的cys帮助;叉cys做法时并没有构造反例。

你可能感兴趣的:(比赛合集)