zoukankan      html  css  js  c++  java
  • [noip模拟赛2017.7.6]

    全国信息学奥林匹克联赛 ( NOIP2014) 复 赛 模拟题 Day2 [长乐一中]

    转自同组的一个OIer

    题目名称 改造二叉树 数字对 交换
    英文名称 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.

    题解

    首先求出这颗二叉树的中序遍历,那么问题就转换成用最少的修改次数使这个整数序列严格单调递增。于是很自然的想到了LIS,但单纯用LIS是有一些问题的, 比如这种情况:2 3 1 4, LIS为2 3 4,答案求出来为1,但由于整数的限制,应该 要修改2次。即直接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
    ",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));
    }
    
    
  • 相关阅读:
    C#时间差
    centos8安装ffmpeg
    CentOS8同步时间
    安装Supervisor
    ajax 传递 token
    .net core 3.1 中 的跨域设置
    jaeger 本地编译
    Kubernates 环境搭建
    linux : find
    Linux: 文件分割和合并
  • 原文地址:https://www.cnblogs.com/DexterYsw/p/7191371.html
Copyright © 2011-2022 走看看