zoukankan      html  css  js  c++  java
  • [做题笔记] 浅谈笛卡尔树结构的应用

    笛卡尔树内核简单,但是应用广泛,和序列规划、计数、最值类问题联系很大。

    SPOJ PERIODNI

    题目描述

    点此看题

    解法

    可以考虑建出笛卡尔树,每个点的管辖范围是高为它的一个极长子矩形,为了防止不同矩形的决策互相影响我们把这个极长子矩形删掉以后再递归到儿子。

    (f[i][j]) 表示以 (i) 为根的子树中选出 (j) 个关键点的方案数,先把子树的方案合并上来:

    [tmp[j]leftarrowsum_{k=0}^jf[ls][k] imes f[rs][j-k] ]

    然后考虑当前这个子矩形的决策,我们考虑先让子树放了车再在当前点的子矩形里面放车,这样就可以消去棋盘每一列的影响,因为笛卡尔树帮助我们去除了行的影响所以可以直接计数,设这个矩形的宽是 (W) 高是 (H),那么有转移:

    [f[i][j]=sum_{k=0}^jtmp[j-k] imes k! imes {Hchoose k} imes {W-(j-k)choose k} ]

    暴力转移时间复杂度 (O(nk^2))

    总结

    笛卡尔树可以帮助消去一些列的限制,考虑每个点管辖范围即可。

    #include <cstdio>
    const int M = 505;
    const int N = 1000005;
    const int MOD = 1e9+7;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,k,a[M],fac[N],inv[N];
    int ch[M][2],s[M],f[M][M],tmp[M];
    void init(int n)
    {
    	inv[0]=inv[1]=fac[0]=1;
    	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
    	for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
    	for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
    }
    int C(int n,int m)
    {
    	if(n<m || m<0) return 0;
    	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
    }
    int dfs(int u,int t)
    {
    	if(!u)
    	{
    		f[u][0]=1;
    		return 0;
    	}
    	int H=a[u]-t,W=1;
    	W+=dfs(ch[u][0],a[u]);
    	W+=dfs(ch[u][1],a[u]);
    	for(int i=0;i<=W;i++) tmp[i]=0;
    	for(int i=0;i<=W;i++)
    		for(int j=0;i+j<=W;j++)
    			tmp[i+j]=(tmp[i+j]
    			+f[ch[u][0]][i]*f[ch[u][1]][j])%MOD;
    	for(int i=0;i<=W;i++)
    		for(int j=0;j<=i;j++)
    			f[u][i]=(f[u][i]+tmp[i-j]*fac[j]%MOD
    			*C(H,j)%MOD*C(W-(i-j),j))%MOD;
    	return W;
    }
    signed main()
    {
    	n=read();k=read();init(1e6);
    	for(int i=1;i<=n;i++)
    	{
    		a[i]=read();
    		while(m && a[s[m]]>=a[i])
    			ch[i][0]=s[m],m--;
    		if(m) ch[s[m]][1]=i;
    		s[++m]=i;
    	}
    	dfs(s[1],0);
    	printf("%lld
    ",f[s[1]][k]);
    }
    

    Histogram Coloring

    题目描述

    点此看题

    解法

    还是笛卡尔树最小值分治的方法,考虑上一层传下来的染色方案怎么接着染,如果是黑白相间的那么每次可以选择复制或者取反,但如果不是黑白相间的就只能取反了。

    这启示我们要分类讨论,设 (dp[u][0/1]) 表示 (u) 的子树内,是黑白相间(/)任意染色(包含黑白相间)的方案数,设这一层的最小值有 (m) 个,矩形高是 (x)(广义笛卡尔树,把所有最小值取出来划分成多个区间):

    (dp[u][0]leftarrow 2^xprod dp[v][0]),也就是考虑枚举每个高度初始是 R/B,那么本层就有唯一的状态,且上一层的任何状态都能合法地转移下来。

    (dp[u][1]leftarrow 2^mprod(dp[v][0]+dp[v][1])+(2^x-2)prod dp[v][0]),第一项的意思是最小值的位置初始可以任意染色,然后如果儿子黑白相间那么初始有复制和取反两种选择,否则只能取反;第二项是计算当前还是黑白相间的染色方案,由于初始 R/B 打头的黑白相间方案各被第一项计算了一次,所以要减去。

    时间复杂度 (O(n^2+nlog h))

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int M = 1005;
    const int MOD = 1e9+7;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,rt,cnt,a[M],f[M][2];
    int qkpow(int a,int b)
    {
    	int r=1;
    	while(b>0)
    	{
    		if(b&1) r=r*a%MOD;
    		a=a*a%MOD;
    		b>>=1;
    	}
    	return r;
    }
    void dfs(int &x,int l,int r,int c)
    {
    	x=++cnt;f[x][0]=1;
    	if(l>r) return ;
    	int mi=1e9,m=0,p[r-l+5]={};
    	for(int i=l;i<=r;i++)
    		mi=min(mi,a[i]);
    	p[++m]=l-1;
    	for(int i=l;i<=r;i++)
    		if(a[i]==mi) p[++m]=i;
    	p[++m]=r+1;f[x][1]=1;
    	for(int i=2;i<=m;i++)
    	{
    		int s=0;
    		dfs(s,p[i-1]+1,p[i]-1,mi);
    		f[x][0]=f[x][0]*f[s][0]%MOD;
    		f[x][1]=f[x][1]*(f[s][0]+f[s][1])%MOD;
    	}
    	int t1=qkpow(2,m-2),t2=qkpow(2,mi-c);
    	f[x][1]=(t1*f[x][1]+(t2-2)*f[x][0])%MOD;
    	f[x][0]=t2*f[x][0]%MOD;
    	return ;
    }
    signed main()
    {
    	n=read();
    	for(int i=1;i<=n;i++)
    		a[i]=read();
    	dfs(rt,1,n,0);
    	printf("%lld
    ",(f[rt][1]+MOD)%MOD);
    }
    

    Tree Depth P

    题目描述

    点此看题

    解法

    首先可以转一下题意(过程不想写,不会问我):问有 (k) 个逆序对的排列构造出的笛卡尔树每个位置的深度在所有情况下的总和。

    直接 (dp) 估摸着要 (n^6),而且难以优化。笛卡尔树有一些很好的性质,首先我们把点 (u) 的深度拆个贡献:

    [dep_u=sum_{v} [lca(u,v)=v] ]

    考虑 (v)(u) 祖先的充要条件:([min(u,v),max(u,v)]) 这段区间中 (p_v) 是最小值。那么我们要算的满足上述条件的排列个数,然后把这个方案记在 (u) 上面即可,首先我们可以写出没有限制的生成函数:

    [prod_{i=1}^{n-1}sum_{j=0}^i x^j ]

    然后考虑这个限制的影响,我们可以先除去这个区间和 (v) 构成的逆序对(也就是做一个退背包),如果 (v<u) 那么 (v) 作为最小值对区间逆序对的影响是 (0),如果 (u<v) 那么 (v) 作为最小值对区间逆序对的影响是 (v-u),直接找对应项的系数即可。

    具体实现过程中要做前缀和优化的背包和退背包,不用真的枚举区间,只需要枚举区间长度就可以了,时间复杂度 (O(nk)),记得最后要加上 (u=v) 的贡献。

    总结

    深度可以拆贡献,也就是拆成某个点是另一个点的 (lca)

    #include <cstdio>
    #include <cstring>
    const int M = 90005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,k,nw,f[M],g[M],ans[M];
    void add(int &x,int y) {x=(x+y)%m;}
    void sub(int &x,int y) {x=(x-y+m)%m;}
    void mul(int x)
    {
    	memset(g,0,sizeof g);
    	for(int i=0;i<=nw;i++) g[i]=f[i];
    	memset(f,0,sizeof f);
    	nw+=x;
    	for(int i=1;i<=nw;i++) add(g[i],g[i-1]);
    	for(int i=0;i<=nw;i++)
    	{
    		add(f[i],g[i]);
    		if(i>x) sub(f[i],g[i-x-1]);
    	}
    }
    void div(int x)
    {//f[i]=g[i]-g[i-x-1]->g[i]=f[i]+g[i-x-1]
    	memset(g,0,sizeof g);
    	for(int i=0;i<=nw;i++)
    	{
    		add(g[i],f[i]);
    		if(i>x) add(g[i],g[i-x-1]);
    	}
    	nw-=x;
    	memset(f,0,sizeof f);
    	for(int i=0;i<=nw;i++)
    	{
    		add(f[i],g[i]);
    		if(i) sub(f[i],g[i-1]);
    	}
    }
    signed main()
    {
    	n=read();k=read();m=read();
    	f[0]=1;
    	for(int i=1;i<n;i++) mul(i);
    	for(int i=1;i<n;i++)
    	{
    		div(i);
    		for(int j=i+1;j<=n;j++)
    			add(ans[j],f[k]);
    		for(int j=1;j<=n-i;j++)
    			if(k>=i) add(ans[j],f[k-i]);
    		mul(i);
    	}
    	for(int i=1;i<=n;i++)
    		printf("%lld ",(ans[i]+f[k])%m);
    }
    
  • 相关阅读:
    连接数据库及出现System.AccessViolationException错误的解决方法
    WCF REST 工作总结
    jquery easyui 扩展验证
    正则表达式语法
    SQL select语句执行顺序
    添加头文件afxwin.h后引起异常的解决办法
    imagej基本操作
    医学图像处理(一)
    灰度图像的自动阈值分割(Otsu 法)
    关于glog使用中遇到的问题
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15216199.html
Copyright © 2011-2022 走看看