[noip模拟赛2017.7.6]

题目名称 改造二叉树 数字对 交换
英文名称 binary pair swap
输入文件名 binary.in pair.in swap.in
输出文件名 binary.out pair.out swap.out
时间限制 1s 2s 1s
空间限制 256M 256M 256M
测试点数目 20 20 10
测试点分值 5 5 10
是否有部分分
题目类型 传统 传统 传统
是否有 SPJ

改造二叉树

【题目描述】

小Y在学树论时看到了有关二叉树的介绍:在计算机科学中,二叉树是每个结点最多有 两个子结点的有序树。通常子结点被称作“左孩子”和“右孩子”。二叉树被用作二叉搜索 树和二叉堆。随后他又和他人讨论起了二叉搜索树。 什么是二叉搜索树呢?二叉搜索树首先是一棵二叉树。设key[p]表示结点p上的数值。 对于其中的每个结点p,若其存在左孩子lch,则key[p]>key[lch];若其存在右孩子rch,则 key[p]<key[rch];注意,本题中的二叉搜索树应满足对于所有结点,其左子树中的key小于 当前结点的key,其右子树中的key大于当前结点的key。 小Y与他人讨论的内容则是,现在给定一棵二叉树,可以任意修改结点的数值。修改一 个结点的数值算作一次修改,且这个结点不能再被修改。若要将其变成一棵二叉搜索树,且 任意时刻结点的数值必须是整数(可以是负整数或0) ,所要的最少修改次数。 相信这一定难不倒你!请帮助小Y解决这个问题吧。

【输入格式】

第一行一个正整数 n 表示二叉树结点数。结点从 1~n 进行编号。 第二行 n 个正整数用空格分隔开,第 i 个数 ai 表示结点 i 的原始数值。 此后 n-1 行每行两个非负整数 fa,ch,第 i+2 行描述结点 i+1 的父亲编号 fa,以及父 子关系 ch,(ch=0 表示 i+1 为左儿子,ch=1 表示 i+1 为右儿子)。 结点 1 一定是二叉树的根。

【输出格式】

仅一行包含一个整数,表示最少的修改次数。

【样例输入】

3

2 2 2

1 0

1 1

【样例输出】

2

【数据范围】

20% :n<=10,ai<=100.

40% :n<=100,ai<=200.

60% :n<=2000.

100% :n<=10^5, ai<2^31.

题解

将二叉树的中序遍历存到数组A里面,
每个A[i]-=i,求取A[i]的LIS(最长非降子序列),
答案即为n-LIS,下面是AC代码:


#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>

using namespace std;
struct node{
	int l,r,f,v;
	node(){l=0;r=0;f=0;v=0;}
}T[100100];
int n;
void link(int xs,int xf,int ch){
	T[xs].f=xf;
	if(ch==0)
		T[xf].l=xs;
	else T[xf].r=xs;
}
int a[100100],cnt;
void dfs(int k){
	int ls=T[k].l,rs=T[k].r;
	if(ls)dfs(ls);
	a[++cnt]=T[k].v-cnt;
	if(rs)dfs(rs);
}
int best[100100],nn;
int grade[100100];
int bound(int val){
	int l=1,r=nn+1;
	while(l<r){
		int mid=(l+r)/2;
		if(val>best[mid])l=mid+1;
		else if(val<best[mid])r=mid;
		else l=mid+1;
	}
	if(l==nn+1)nn++;
	return l;
}
int LIS(){
	for(int i=1;i<=n;i++)best[i]=2147483647;
	for(int i=1;i<=n;i++){
		int k=bound(a[i]);
		best[k]=a[i];
		grade[i]=k;
	}
	int rtn=0;
	for(int i=1;i<=n;i++)
		rtn=max(rtn,grade[i]);
	return rtn;
}
int main(){
	freopen("binary.in","r",stdin);
	freopen("binary.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&T[i].v);
	for(int i=2;i<=n;i++){
		int x,y;scanf("%d%d",&x,&y);
		link(i,x,y);
	}
	dfs(1);
	printf("%d",n-LIS());
}
/*
3 
2 2 2
1 0 
1 1
*/

数字对

【题目描述】

小 H 是个善于思考的学生,现在她又在思考一个有关序列的问题。 她的面前浮现出一个长度为 n 的序列{ai},她想找出一段区间L,R。 这个特殊区间满足,存在一个 k(L<=k <=R),并且对于任意的 i(L<=i<=R),ai 都能 被 ak 整除。这样的一个特殊区间 [L,R]价值为 R-L。 小 H 想知道序列中所有特殊区间的最大价值是多少,而有多少个这样的区间呢?这些 区间又分别是哪些呢?你能帮助她吧。

【输入格式】

第一行,一个整数 n. 第二行,n 个整数,代表 ai.

【输出格式】

第一行两个整数,num 和 val,表示价值最大的特殊区间的个数以及最大价值。 第二行 num 个整数,按升序输出每个价值最大的特殊区间的 L.

【样例输入 1】

5

4 6 9 3 6

【样例输出 1】

1 3

2

【样例输入 2】

5

2 3 5 7 11

【样例输出 2】

5 0

1 2 3 4 5

【数据范围】

30%: 1 <= n <= 30 , 1 <= ai <= 32.

60%: 1 <= n <= 3000 , 1 <= ai <= 1024.

80%: 1 <= n <= 300000 , 1 <= ai <= 1048576.

100%: 1 <= n <= 500000 , 1 <= ai < 2 ^ 31.

题解

这道题数据比较弱,虽然有50W,但O(n^2)的暴力可以AC,就是每个点左右扩展,然后就能过,这里就不细说了,本题正解可以用RMQ预处理,然后既可以O(1)查询区间[L,R]的Min以及Gcd,如果一个区间的Min等于Gcd,那么这个区间就是符合要求的,我们二分区间长度,O(n)检查该长度的满足条件的区间数量,并随时记录保存即可,下面是AC代码(在时间效率上不如暴力):

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>

using namespace std;
int n,a[500500];
int gcd(int x,int y){
	if(y==0)return x;
	return gcd(y,x%y);
}
int Min[500500][20];
int Gcd[500500][20];

void RMQ(){
	for(int i=1;i<=n;i++){
		Min[i][0]=a[i];
		Gcd[i][0]=a[i];
	}
	for(int i=1;(1<<i)<=n;i++){
		for(int j=1;j+(1<<i)-1<=n;j++){
			Min[j][i]=min(Min[j][i-1],Min[j+(1<<(i-1))][i-1]);
			Gcd[j][i]=gcd(Gcd[j][i-1],Gcd[j+(1<<(i-1))][i-1]);
		}
	}
}
int ans[500500],tot;
bool check(int L){
	tot=0;
	int k=0;while((1<<k)<=L)k++;k--;
	for(int i=1;i+L<=n;i++){
		int G=gcd(Gcd[i][k],Gcd[i+L-(1<<k)+1][k]);
		int M=min(Min[i][k],Min[i+L-(1<<k)+1][k]);
		if(G==M)ans[++tot]=i;
	}
	return tot;
}
int main(){
	freopen("pair.in","r",stdin);
	freopen("pair.out","w",stdout);
	scanf("%d",&n);tot=n;
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	RMQ();
	int l=0,r=n;
	while(l<r){
		int mid=(l+r)/2+1;
		bool res=check(mid);
		if(res)l=mid;
		else r=mid-1;
	}
	check(l);
	printf("%d %d\n",tot,l);
	for(int i=1;i<=tot;i++)
		printf("%d ",ans[i]);
}

交换

【题目描述】

给定一个{0,1,2,3,…,n-1}的排列 p。一个{0,1,2 ,…,n-2}的排列 q 被认为是优美 的排列,当且仅当 q 满足下列条件: 对排列 s={0,1,2,3,...,n-1}进行 n–1 次交换。 1. 交换 s[q0],s[q0+1] 2. 交换 s[q1],s[q1+1] … 最后能使得排列 s=p. 问有多少个优美的排列,答案对 10^9+7 取模。

【输入格式】

第一行一个正整数 n. 第二行 n 个整数代表排列 p.

【输出格式】

仅一行表示答案。

【样例输入】

3

1 2 0

【样例输出】

1

【样例解释】

q={0,1}{0,1,2}->{1,0,2}->{1,2,0}

q={1,0}{0,1,2}->{0,2,1}->{2,0,1}

【数据范围】

30%: n<=10

100%: n<=50

题解

考虑将P排列还原成S,每次在[l,r]中枚举最后交换的两个数(P[m],P[m+1]),可以证明,在此操作前,区间[l,m]中的数不可能到达区间[m+1,r]中去,所以要判断(p[m],p[m+1])可以交换,必须满足交换之后[l,m]的值都小于[m+1,r]的值,然后递归处理两段区间,在时序上两段区间的操作组合C(r-l-1,m-l)也要乘上去,下面是AC代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#define mod 1000000007
#define LL long long 
using namespace std;
int n,p[100];
int dp[100][100];
bool fg[100][100];
int c[100][100];
int szsz[100];
int lowbit(int x){return x&(-x);}
void updata(int x,int v){
	while(x<=n){
		szsz[x]+=v;
		x+=lowbit(x);
	}
}
int query(int x){
	int rtn=0;
	while(x){
		rtn+=szsz[x];
		x-=lowbit(x);
	}
	return rtn;
}
int getinv(){
	int rtn=0;
	for(int i=1;i<=n;i++){
		int k=query(p[i]);
		rtn+=(i-k-1);
		updata(p[i],1);
	}
	return rtn;
}
void pre(){
	for(int i=0;i<=75;i++){
		c[i][0]=1;
		for(int j=1;j<=i;j++)
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
	}
}
int dfs(int l,int r){
	if(fg[l][r])return dp[l][r];
	fg[l][r]=true;
	int Max[100],Min[100];
	for(int i=0;i<=n+1;i++)
		Max[i]=0,Min[i]=2147483647;
	for(int i=l;i<r;i++)
		Max[i]=max(Max[i-1],p[i]);
	for(int i=r;i>l;i--)
		Min[i]=min(Min[i+1],p[i]);
	int rtn=0;
	for(int m=l;m<r;m++){
		swap(p[m],p[m+1]);
		int m1=max(Max[m-1],p[m]);
		int m2=min(Min[m+2],p[m+1]);
		if(m1<m2)
			rtn=(rtn+(((LL)dfs(l,m)*dfs(m+1,r))%mod*c[r-l-1][m-l])%mod)%mod; 
		swap(p[m],p[m+1]);
	}
	return dp[l][r]=rtn;	
}
int main(){
	pre();
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&p[i]),p[i]++,fg[i][i]=true,dp[i][i]=1;
	if(getinv()!=n-1){
		cout<<0;
		return 0;
	}
	printf("%d",dfs(1,n));
}

posted @ 2017-07-08 10:59  Anoxiacxy  阅读(763)  评论(0编辑  收藏  举报