10/24 某模拟赛

不想说什么么?

说点吧,毕竟这题做的我还是比较正常的。

很好的诠释了什么叫想到了正解,然后打不出来的窘境。。。

所以我是不是又要去hzwer的博客里找题看了呢?

好了一道一道说题:

A.改造二叉树

题面

「题目描述」

小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\)个数\(a_i\)表示结点\(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,a_i <= 100.\)

\(40%\)%:\(n <= 100,a_i <= 200.\)

\(60%\)%:\(n <= 2000.\)

\(100%\)%:\(n <= 10 ^ 5 , a_i < 2 ^ {31}.\)

分析:

这题一眼觉得确实不是特别好做,二叉搜索树,像是一个树形DP,仔细看看发现没法维护。

观察二叉搜索树的形态,发现中序遍历之后会得到一个上升序列。

然后就可以先把它的中序遍历存起来,然后跑一个最长上升子序列就可以了,答案就是\(n-ans\)

有一个小问题就是对于两个数他们差的大小要大于他们下标的差的大小。。。

把他们的\(val\)和下标做差后在跑\(Lis\)就可以了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define ll long long
const int MAXN=1e5+7;
struct tree
{
	int l,r,val;
}a[MAXN];
int n,m,t,ans,s[MAXN],l,f[MAXN],cnt,maxx,w[MAXN];
inline int read()
{
    int x=0,c=1;char ch=' ';
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    while(ch=='-') c*=-1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*c;
}
void dfs(int x)
{
	if(a[x].l!=0) dfs(a[x].l);
	s[++cnt]=a[x].val-cnt;
	if(a[x].r!=0) dfs(a[x].r);
}
int find(int x)
{
	int l=1,r=ans;
	while(l<=r){
		int mid=(l+r)>>1;
		if(w[mid]<=x) l=mid+1;
		else r=mid-1;
	}
	return l-1;
}
int main()
{
	freopen("binary.in","r",stdin);
	freopen("binary.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++) a[i].val=read(),f[i]=1;
	for(int i=2;i<=n;i++) {
		int ff,lr;
		ff=read(),lr=read();
		if(lr==0) a[ff].l=i;
		else a[ff].r=i;
	}
	dfs(1);
	for(int i=1;i<=n;i++) w[i]=2147483647;
	w[0]=0;
	for(int i=1;i<=n;i++){
		int t=find(s[i]);
		w[t+1]=s[i];
		ans=max(ans,t+1);
	}
	cout<<n-ans;
}

B.数字对

题面

「题目描述」

小H是个善于思考的学生,现在她又在思考一个有关序列的问题。

她的面前浮现出一个长度为\(n\)的序列\({a_i}\),她想找出一段区间\([L, R](1 \le L \le R \le n)\)

这个特殊区间满足,存在一个\(k\)\((L \le k \le R)\),并且对于任意的\(i\)\((L \le i \le R)\)\(a_i\)都能被\(a_k\)整除。这样的一个特殊区间 \([L, R]\)价值为\(R – L\)

小H想知道序列中所有特殊区间的最大价值是多少,而有多少个这样的区间呢?这些区间又分别是哪些呢?你能帮助她吧。

「输入格式」

第一行,一个整数\(n\).

第二行,\(n\)个整数,代表\(a_i\).

「输出格式」

第一行两个整数,\(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 \le n \le 30 , 1 \le a_i \le 32.\)

60%: \(1 \le n \le 3000 , 1 \le a_i \le 1024.\)

80%: \(1 \le n \le 300000 , 1 \le a_i \le 1048576.\)

100%😒 1 \le n \le 500000 , 1 \le a_i < 2 ^ {31}.$

真正の题解

30% :暴力枚举判断。\(O(n^4)\) or \(O(n^3)\)

60% :特殊区间的特点实际上就是区间最小值等于这个区间的\(GCD\),于是暴力或递推算出每个区间最小值与\(GCD\)。而对于最大价值,可以通过二分来进行求解。复杂度\(O(n ^ 2)\)

100%:在60%的基础上,最小值与\(GCD\)都使用\(RMQ\)算法来求解,对于这道题推荐使用ST表,线段树会被艹飞。复杂度\(O(nlogn)\)

以上选自下发的题解中抄袭的黄学长的题解。

我の题解

观察,首先知道一个数的因数是log级的(可能也不是,反例:200560490130有好多好多因数呢),反正非常少就是了。

继续观察发现,假设我们现在有一个数在一个最长的序列里,并且那个能整除他的数在他的右边,那么是不是就没有第二个序列保证最长同时能整除他的数在他右边,左边同理。

好,那么得出:先按照从小到大排序,然后一个一个向左右两边拓展,拓展到的点打上标记,表示不会从这个点开始拓展,这样的话每个点最多只会被它左边或者右边的一个点拓展到,所以复杂度是\(O(n)\)的。

当然你可以不排序,这样整体复杂度就是\(O(n)\)的了。

艹翻上面ST表,我能跑\(4e6\),你连数组都开不下。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define ll long long
const int MAXN=5e5+7;
inline int read()
{
    int x=0,c=1;char ch=' ';
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    while(ch=='-') c*=-1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*c;
}
int n,ans,val,num[MAXN],b[MAXN],d[MAXN],vis[MAXN],nm,maxx;
struct point
{
	int id,v,l,r;
}a[MAXN];
inline bool cmp(point x,point y){return x.v<y.v;}
int main()
{
	freopen("pair.in","r",stdin);
	freopen("pair.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++){
		d[i]=a[i].v=read();
		a[i].id=i;
	}
	for(int i=1;i<=n;i++){
		a[i].l=i;
		while(d[i+1]==d[i]){
			i++;
			a[i].l=a[i-1].l;
		}
	}
	for(int i=n;i>=1;i--){
		a[i].r=i;
		while(d[i-1]==d[i]){
			i--;
			a[i].r=a[i+1].r;
		}
	}
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++){
		int now=a[i].id,cnt=1,l=now,r=now;
		if(b[now]) continue;
		while(d[l-1]%d[now]==0&&l>1){
			if(vis[l-1]==d[now]) break;
			vis[l-1]=d[now];
			b[l-1]=1;
			cnt++;l--;
		}
		while(d[r+1]%d[now]==0&&r<n){
			if(vis[r+1]==d[now]) break;
			vis[r+1]=d[now];
			b[r+1]=1;
			cnt++;r++;
		}
		if(maxx<cnt){
			nm=0;
			maxx=cnt;
			num[++nm]=l;
		} else if(maxx==cnt){
			num[++nm]=l;
		}
	}
	sort(num+1,num+nm+1);
	cout<<nm<<" "<<maxx-1<<endl;
	for(int i=1;i<=nm;i++){
		printf("%d ", num[i]);
	}
}

C.交换

题面

【题目描述】
给定一个\(\lbrace0, 1, 2, 3, … , n - 1\rbrace\)的排列 \(p\)。一个\(\lbrace0, 1, 2 , … , n - 2\rbrace\)的排列\(q\)被认为是优美的排列,当且仅当\(q\)满足下列条件:
对排列\(s =\lbrace 0, 1, 2, 3, ..., n - 1\rbrace\)进行\(n – 1\)次交换。

  1. 交换\(s[q_0]\),\(s[q_0 + 1]\)
  2. 交换\(s[q_1]\),\(s[q_1 + 1]\)

    最后能使得排列s = p.
    问有多少个优美的排列,答案对10^9+7取模。

【输入格式】
第一行一个正整数n.
第二行n个整数代表排列p.

【输出格式】
仅一行表示答案。

【样例输入】

3
1 2 0

【样例输出】

1

【样例解释】
\(q = \lbrace 0,1 \rbrace \lbrace 0,1,2\rbrace \to\lbrace1,0,2\rbrace \to \lbrace1, 2, 0 \rbrace\)
$q = \lbrace1,0\rbrace \lbrace0,1,2\rbrace \to \lbrace0,2,1\rbrace \to \lbrace2, 0, 1\rbrace $

【数据范围】
30%: \(n <= 10\)
100%: \(n <= 50\)

真正の题解:

考虑倒着处理, 比如交换 \((i, i + 1)\), 那么前面的所有数不管怎么交换都无法到后面去(下
标恒小于等于$ i$),后面的数也是一样到不了前面。说明这最后一次交换前,就要求对于所有
\(x \le i\), \(y > i\)\(p_x < p_y\)。所以交换前左边的数是连续的,右边也是连续的。由于交换前, 前
面和后面的数是互相不干涉的,所以就归结成了两个子问题。于是我们可以用记忆化搜索来
解决这个问题。
\(dp[n][low]\) 代表长度为 \(n\)\(H\)\(\lbrace low, low + 1,…,low + n - 1\rbrace\)的排列,且 \(H\)\(p\)的子序
列,在 \(H\) 上优美序列的个数。
我们枚举交换哪两个相邻元素\((k,k+1)\), 然后判断 \(k\) 前面的所有数是否都小于后面的所有
数,如果是则进行转移 \(dp[n][low] += dp[k][low] * dp[n – k][low + k ] * C_{n – 2}^{ n – 1 - k}\)
即前面的$ k$ 个元素与后面的 $n - k \(个元素是两个独立的子问题,前面是\)\lbrace low ... low + k - 1\rbrace\(的 排列,后面是\)\lbrace low + k ... low + n - 1\rbrace\(的排列,\)C_{n – 2}^{ n – 1 - k}\(代表的是在交换\)(k, k + 1)$前左
右两边还分别要进行 \(n - 2\)次交换,而每次交换左边与交换右边是不同方案,这相当于$ n - 2\( 个位置选择\) n - 1 - k$ 个位置填入,故还需要乘上 \(C_{n – 2}^{ n – 1 - k}\)。时间复杂度为 \(O(n^4)\)

我のYY:

我(看到题解后):woc,这咋还DP呢??woc,这复杂度怎么\(O(n^4)\)的呢。明明拓扑排序求个拓扑序列个数就行了啊。。。为啥多三个\(n\)啊???
然而并没有在比赛的时候写出来,只拿了30分暴力。。。

posted @ 2018-10-24 15:06  ~victorique~  阅读(206)  评论(1编辑  收藏  举报
Live2D