为这狭路相逢 深鞠一躬

陈词旧句都讽诵
愈掷地有声愈动容
岁月形色倥偬 雕梁画栋
从无名之辈到最终
座下万千人一众
为这狭路相逢 深鞠一躬
\(~~~~~~~~~~~~~~~~~~~~~~\)——《梨园小记》司南、万象凡音

「CEOI2022」Homework

题意

\(~~~~\) 给一个包含 \(\min,\max\)\(?\) 的表达式,设 \(?\) 个数为 \(n\) ,将 \(n\) 个问号均填成 \(1 \sim n\) 内的数且不重复,求值域大小。
\(~~~~\) \(1\leq n\leq 10^6\)

题解

\(~~~~\) 一个一个 \(\text{sub}\) 来看:

Sub1

\(~~~~\) \(1\leq N\leq 9\)

\(~~~~\) 显然枚举排列即可。

Sub2

\(~~~~\) \(1\leq N\leq 16\)

\(~~~~\) 不难发现我们可以把表达式转化成一棵二叉树,树上所有叶子结点应填入一个排列;非叶子结点有一个 \(\max\)\(\min\) 的类型,且权值为两个子结点的权值取 \(\max\)\(\min\) 。那我们要做的就是求出根结点可能的权值。

\(~~~~\) \(N\) 的范围有一个很好的状压提示性。记 \(dp_{u,S}\) 表示在 \(u\) 结点,其内部叶子结点填入的状态为 \(S\) 时的可行取值,那对于每个非叶子结点枚举 \(S\) 后划分成两个子集,合并两个叶子的答案即可。复杂度应该在 \(\mathcal{O(3^n·n^2)}\) ,但是显然枚举的状态应该满足子树内叶子个数和它的 \(1\) 的个数的对应关系,无效状态很多,所以应该可以过。

Sub3

\(~~~~\) 所有 \(\min\)\(\max\) 内必定有一侧为 \(?\)

\(~~~~\) 从这里开始不那么无脑了。此时我们不妨把连续的 \(\min\)\(\max\) 操作的叶子全部堆到一起,那非叶子结点的种类就会是 \(\min\)\(\max\) 交错。下面我们以根结点 \(\min\) 为例:假设它的下面堆了 \(k+1\) 个结点,其中 \(k\) 个为叶子结点,另一个为 \(\max\) 结点(不考虑没有 \(\max\) 结点的情况),那显然最大的 \(k\) 个必然选不到。剩下的 \(n-k\) 个呢?它们就必然可以选得到。

\(~~~~\) 归纳证明这个结论:对于子结点中没有非叶子结点的点来说,该结论(即对于该点 \(\min\) 只能选到最小的 \(k+1-k=1\) 个 ,\(\max\) 只能选到最大的 \(k+1-k=1\) 个 )显然成立。然后对于任意子结点中有非叶子结点的点,若其为 \(\min\),在其中一个结点填入待取的值,其余叶子结点填入比它大的值,并且把至少一个比它大的数留给非叶子结点,那显然由于可以取到最大的若干个(至少一个),\(\max\) 结点的结果也一定比它大。所以这样的数是可以取到的。而填入 \(n-k\) 后剩余 \(k\) 个比它大的数,刚好满足条件,所以 \(1\sim n-k\) 全部可以取到。当然 \(\max\) 结点同理可证。

Sub 4,5

\(~~~~\) \(1\leq N\leq 10^3.1\leq N\leq 10^6\)

\(~~~~\) 不会 \(n^2\) 直接讲正解吧。

\(~~~~\) 通过以上三个 sub 的任意一种我们都可以猜测:值域必定为一个连续区间。具体证明也可以考虑归纳法。

\(~~~~\) 所以我们来考虑:设某个结点左子树有 \(siz_L\) 个叶子结点,右子树有 \(siz_R\) 个叶子结点。且左子树填入 \([1,siz_L]\) 的排列,右子树填入 \([1,siz_R]\) 的排列分别的取值范围为 \([l_1,r_1],[l_2,r_2]\)

  • 当该结点为 \(\min\) 结点时:新区间为 \([\min(l_1,L_2),r_1+r_2-1]\)
  • 当该结点为 \(\max\) 结点时:新区间为 \([l_1+l_2,\max(siz_r+r_1,siz_l+r_2)]\)

\(~~~~\) 这是可以理论上去证明的,但笔者要吃饭了所以不证了。不过个人认为最好的办法还是直接打表找规律。(总之就是一道动手题)

\(~~~~\) 那我们就直接 \(\mathcal{O(n)}\) 做树形DP即可。

代码

\(~~~~\) 出奇地简单好写。

#include <bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x)
{
    T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    x*=f;
}
int rt,tot;
int Type[10000005],Mat[10000005];
int ch[10000005][2];
char Str[10000005];
int Build(int l,int r)
{
	int now=++tot;
	if(l==r) return tot;
	if(Str[l+1]=='i') Type[now]=-1;
	else Type[now]=1;
	ch[now][0]=Build(l+4,Mat[l+3]-1);
	ch[now][1]=Build(Mat[l+3]+1,r-1);
	return now;
}
int siz[10000005];
int L[10000005],R[10000005];
#define ls ch[u][0]
#define rs ch[u][1]
void dfs(int u)
{
	if(!ls&&!rs) {L[u]=R[u]=siz[u]=1;return;}
	dfs(ls); dfs(rs);
	siz[u]+=siz[ls]; siz[u]+=siz[rs];
	if(Type[u]==-1) L[u]=min(L[ls],L[rs]),R[u]=R[ls]+R[rs]-1;
	else L[u]=L[ls]+L[rs],R[u]=max(R[ls]+siz[rs],R[rs]+siz[ls]);
}
#undef ls
#undef rs
int main() {
	scanf("%s",Str+1);
	int len=strlen(Str+1);
	stack <int> S;
	for(int i=1;i<=len;i++)
	{
		if(Str[i]=='(') S.push(i);
		if(Str[i]==')') S.pop();
		if(Str[i]==',') Mat[S.top()]=i;
	}
	rt=Build(1,len);
	dfs(rt); printf("%d",R[rt]-L[rt]+1);
	return 0;
}
posted @ 2022-08-11 19:37  Azazеl  阅读(83)  评论(0编辑  收藏  举报