[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));
}