zoukankan      html  css  js  c++  java
  • 「HNOI2019」多边形(树形dp)

    「HNOI2019」多边形(树形dp)

    题目给出的操作和条件都非常抽象,这意味着我们需要极大程度上精简这个问题

    \(\rightarrow\)序列

    首先,我们将环达成\(1..n\)的序列,每条边就是一个区间,暂且保留所有多边形原本的边

    每条边在多边形上都是把区域分成两部分,所以在序列上每条边都是把一个区间\([L,R]\)分成\([L,mid],[mid,R]\),直到最后\(L+1=R\)

    \[\ \]

    序列$\rightarrow $树

    考虑这个分解的过程 ,事实上就是一个构建二叉树的过程,其中\(L+1=R\)的节点就是叶子

    那么得到一些简单性质\(L_{lson}=L_{father},R_{rson}=R_{father},R_{father}>R_{lson},L_{rson}>L_{father}\)

    根据这四个性质,对于每个\(L\),对于每个\(R\)分别做一下,就能够构造出一棵二叉树了

    最后得到的大概是这个样子(对应题目样例1)

    无标题.png

    建树代码(这里直接省略了叶子节点)

    	W=rd(),n=rd();
    	A[++cnt]=(Node){1,n,cnt}; // 加入根
    	rep(i,1,n-3) ++cnt,A[cnt].l=rd(),A[cnt].r=rd(),A[cnt].id=cnt; // 加入节点
    	rep(i,1,cnt) M[A[i].l][A[i].r]=i;
    	sort(A+1,A+cnt+1,cmp1);
    	rep(i,1,cnt) {
    		int j=i;
    		while(j<n && A[j+1].l==A[j].l) ++j;
    		rep(k,i,j-1) ls[A[k].id]=A[k+1].id,fa[A[k+1].id]=A[k].id;
    		i=j;
    	}
    	sort(A+1,A+cnt+1,cmp2);
    	rep(i,1,cnt) {
    		int j=i;
    		while(j<n && A[j+1].r==A[j].r) ++j;
    		rep(k,i,j-1) rs[A[k].id]=A[k+1].id,fa[A[k+1].id]=A[k].id;
    		i=j;
    	} // 两次sort确定父子关系
    	sort(A+1,A+cnt+1,[&](Node x,Node y){ return x.id<y.id; });
    	// 找回原来的顺序
    

    \[\ \]

    操作\((a,b,c,d)\)

    观察上面的这个树

    那么一个操作\((a,b,c,d)\)可行的条件就是:对于一个非叶子且非根的节点,它是父亲的左儿子

    所以可以非常显然地得到,最后得到的所有边就是\([i,i+1]\)\([i,n]\),让一整棵树变成一条右儿子链

    \[\ \]

    最少操作次数

    考虑每一次操作,对于节点\(x\),操作它意味着

    \(x.rson\leftarrow brother\)

    \(father.lson\leftarrow x.lson\)

    \(father.rson\leftarrow x\)

    把这个节点提上去,自己的左儿子变成父节点的左儿子

    可以看到每次操作过后,你的左儿子又需要操作

    不用操作的点,只有根节点的右儿子链,因为他们永远不可能成为左儿子

    这样我们就知道了最少的操作次数

    \[\ \]

    最少操作的限制

    限制:如果你把一个点\(x\)操作了,而且\(x.father.R\ne n\),那么这一次操作无效,你仍然需要继续被操作

    所以每次操作的点,必须满足\(x.father.R=n\)

    \[\ \]

    \(O(n)\) dp

    考虑建出树之后,进行树形dp求得答案

    1.点x需要被操作

    由于上面的限制,可以发现的是,\(x\)被操作的时间小于\(x.lson,x.rson\),两个儿子之间并没有限制

    所以把这个过程转化为合并两边的操作序列,然后在前面接上\(x\)

    2.点x不需要被操作

    和上面类似,不接上\(x\)即可

    
    struct DPNODE{
    	int x,y;
    	DPNODE(int a=0,int b=1){ x=a,y=b; } 
    	friend DPNODE operator + (const DPNODE a,const DPNODE b){
    		DPNODE res;
    		res.x=a.x+b.x;
    		res.y=1ll*a.y*b.y%P*Fac[res.x]%P*Inv[a.x]%P*Inv[b.x]%P;
            // 合并两个序列,组合数
    		return res;
    	}
    };
    
    DPNODE dfs(int p,int k=0){ // k表示当前点选不选
    	if(!p) return (DPNODE){0,1};
    	DPNODE R=(DPNODE){0,1};
    	if(k) R=dfs(rs[p],1)+dfs(ls[p],1),R.x++;
    	else R=dfs(rs[p],0)+dfs(ls[p],1); // k下传,只有右儿子链不用被操作
    	return R;
    }
    
    void Solve() {
    	DPNODE res=dfs(1);
    	printf("%d %d\n",res.x,res.y);
     	rep(i,1,m){
    		int l=rd(),r=rd(),x=M[l][r]; 
    		if(A[fa[x]].r==n) {
    			DPNODE L=F[x]; L.x--;
    			DPNODE t=G[fa[fa[x]]]+L+F[rs[fa[x]]];
    			printf("%d %d\n",t.x,t.y);
    		} else {
    			DPNODE t=F[rs[x]]+F[rs[fa[x]]];
    			t.x++;
    			t=t+F[ls[x]];
    			printf("%d %lld\n",res.x,res.y*qpow(F[fa[x]].y)%P*t.y%P);
    		}
    	}
    }
    
    
    
    

    \[\ \]

    动态修改一个点

    对于这个点,分成两种情况讨论

    \(x.father.R\ne n\)

    意味着改变之后次数不变,唯一变动的就是合并\(x.father\)的dp值,而向祖先合并的时候,所有的组合数转移都不会发生改变

    我们除掉原来\(x.father\)\(dp\)值,乘上新的\(dp\)值即可

    DPNODE t=F[rs[x]]+F[rs[fa[x]]];
    t.x++;
    t=t+F[ls[x]];// 求出新的dp值
    printf("%d %lld\n",res.x,res.y*qpow(F[fa[x]].y,P-2)%P*t.y%P);
    

    \(x.father.R=n\)

    意味着改变这个点,会是操作次数减\(1\)

    这次合并的过程组合数转移会发生变化,但也仅限于\(x.father\)在根节点的右儿子链上

    所以对于每个右儿子链上的点,我们预处理出一个左儿子操作次数减少时的组合数转移\(G[x]\),最后补上剩下的部分

    namespace pt1{
    	DPNODE F[N],G[N];
    	DPNODE dfs(int p,int k=0){
    		if(!p) return DPNODE();
    		DPNODE R;
    		if(k){
    			DPNODE A=dfs(ls[p],1),B=dfs(rs[p],1);
    			R=A+B,R.x++;
    		}
    		else {
    			DPNODE A=dfs(ls[p],1),B=dfs(rs[p],0);
    			R=A+B;
    			B.x--,B.y=1;
    			G[p]=G[fa[p]]+A+B;
    		}
    		return F[p]=R;
    	}
    	ll qpow(ll x,ll k=P-2) {
    		ll res=1;
    		for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
    		return res;
    	}
    
    	void Solve() {
    		DPNODE res=dfs(1);
    		printf("%d %d\n",res.x,res.y);
    		rep(i,1,m){
    			int l=rd(),r=rd(),x=M[l][r]; 
    			if(A[fa[x]].r==n) {
    				DPNODE L=F[x]; L.x--;
    				DPNODE t=G[fa[fa[x]]]+L+F[rs[fa[x]]];
    				printf("%d %d\n",t.x,t.y);
    			} else {
    				DPNODE t=F[rs[x]]+F[rs[fa[x]]];
    				t.x++;
    				t=t+F[ls[x]];
    				printf("%d %lld\n",res.x,res.y*qpow(F[fa[x]].y)%P*t.y%P);
    			}
    		}
    	}
    }
    

    Code总览

    #include<bits/stdc++.h>
    using namespace std;
    
    #define reg register
    typedef long long ll;
    #define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
    #define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
    
    #define pb push_back
    #define Mod1(x) ((x>=P)&&(x-=P))
    #define Mod2(x) ((x<0)&&(x+=P))
    
    template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
    template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }
    
    char IO;
    int rd(){
    	int s=0,f=0;
    	while(!isdigit(IO=getchar())) if(IO=='-') f=1;
    	do s=(s<<1)+(s<<3)+(IO^'0');
    	while(isdigit(IO=getchar()));
    	return f?-s:s;
    }
    
    const int N=1e5+10,P=1e9+7;
    
    int W,n,m;
    struct Node{
    	int l,r,id;
    }A[N];
    int cnt,ls[N],rs[N],fa[N];
    bool cmp1(Node x,Node y){ return x.l<y.l || (x.l==y.l && x.r>y.r); }
    bool cmp2(Node x,Node y){ return x.r<y.r || (x.r==y.r && x.l<y.l); }
    
    int Inv[N],Fac[N];
    
    struct DPNODE{
    	int x,y;
    	DPNODE(int a=0,int b=1){ x=a,y=b; } 
    	friend DPNODE operator + (const DPNODE a,const DPNODE b){
    		DPNODE res;
    		res.x=a.x+b.x;
    		res.y=1ll*a.y*b.y%P*Fac[res.x]%P*Inv[a.x]%P*Inv[b.x]%P;
    		return res;
    	}
    };
    
    
    map <int,int> M[N];
    
    namespace pt1{
    	DPNODE F[N],G[N];
    	DPNODE dfs(int p,int k=0){
    		if(!p) return DPNODE();
    		DPNODE R;
    		if(k){
    			DPNODE A=dfs(ls[p],1),B=dfs(rs[p],1);
    			R=A+B,R.x++;
    		}
    		else {
    			DPNODE A=dfs(ls[p],1),B=dfs(rs[p],0);
    			R=A+B;
    			B.x--,B.y=1;
    			G[p]=G[fa[p]]+A+B;
    		}
    		return F[p]=R;
    	}
    	ll qpow(ll x,ll k=P-2) {
    		ll res=1;
    		for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
    		return res;
    	}
    
    	void Solve() {
    		DPNODE res=dfs(1);
    		printf("%d %d\n",res.x,res.y);
    		rep(i,1,m){
    			int l=rd(),r=rd(),x=M[l][r]; 
    			if(A[fa[x]].r==n) {
    				DPNODE L=F[x]; L.x--;
    				DPNODE t=G[fa[fa[x]]]+L+F[rs[fa[x]]];
    				printf("%d %d\n",t.x,t.y);
    			} else {
    				DPNODE t=F[rs[x]]+F[rs[fa[x]]];
    				t.x++;
    				t=t+F[ls[x]];
    				printf("%d %lld\n",res.x,res.y*qpow(F[fa[x]].y)%P*t.y%P);
    			}
    		}
    	}
    }
    
    namespace pt2{
    	int dfs(int p,int k=0){
    		if(!p) return 0;
    		int cnt=0;
    		if(k) {
    			++cnt;
    			cnt+=dfs(rs[p],1);
    
    			while(ls[p]) {
    				p=ls[p];
    				++cnt;
    				cnt+=dfs(rs[p],1);
    			}
    		} else {
    			cnt+=dfs(rs[p],0);
    			while(ls[p]) {
    				p=ls[p];
    				++cnt;
    				cnt+=dfs(rs[p],1);
    			}
    		}
    		return cnt;
    	}
    
    	void Solve() {
    		int res=dfs(1);
    		printf("%d\n",res);
    		rep(i,1,m) {
    			int l=rd(),r=rd(),x=M[l][r];
    			printf("%d\n",res-(A[fa[x]].r==n));
    		}
    	}
    }
    
    
    namespace pt3{
    	DPNODE dfs(int p,int k=0){
    		if(!p) return (DPNODE){0,1};
    		DPNODE R=(DPNODE){0,1};
    		if(k) R=dfs(rs[p],1)+dfs(ls[p],1),R.x++;
    		else R=dfs(rs[p],0)+dfs(ls[p],1);
    		return R;
    	}
    
    	void Solve() {
    		DPNODE res=dfs(1);
    		printf("%d %d\n",res.x,res.y);
    		rep(i,1,m){
    			int l=rd(),r=rd(),x=M[l][r];
    			l=ls[x],r=rs[x];
    			int brother=rs[fa[x]];
    
    			rs[x]=brother,rs[fa[x]]=x;
    			ls[x]=r,ls[fa[x]]=l;
    
    			DPNODE res=dfs(1);
    			printf("%d %d\n",res.x,res.y);
    			
    			ls[fa[x]]=x,rs[fa[x]]=brother;
    			ls[x]=l,rs[x]=r;
    		}
    	}
    }
    
    
    int main(){
    
    	Inv[0]=Inv[1]=Fac[0]=Fac[1]=1;
    	rep(i,2,N-1) Inv[i]=1ll*(P-P/i)*Inv[P%i]%P,Fac[i]=1ll*Fac[i-1]*i%P;
    	rep(i,2,N-1) Inv[i]=1ll*Inv[i]*Inv[i-1]%P;
    
    	W=rd(),n=rd();
    	A[++cnt]=(Node){1,n,cnt};
    	rep(i,1,n-3) ++cnt,A[cnt].l=rd(),A[cnt].r=rd(),A[cnt].id=cnt;
    	rep(i,1,cnt) M[A[i].l][A[i].r]=i;
    	sort(A+1,A+cnt+1,cmp1);
    	rep(i,1,cnt) {
    		int j=i;
    		while(j<n && A[j+1].l==A[j].l) ++j;
    		rep(k,i,j-1) ls[A[k].id]=A[k+1].id,fa[A[k+1].id]=A[k].id;
    		i=j;
    	}
    	sort(A+1,A+cnt+1,cmp2);
    	rep(i,1,cnt) {
    		int j=i;
    		while(j<n && A[j+1].r==A[j].r) ++j;
    		rep(k,i,j-1) rs[A[k].id]=A[k+1].id,fa[A[k+1].id]=A[k].id;
    		i=j;
    	}
    	sort(A+1,A+cnt+1,[&](Node x,Node y){ return x.id<y.id; });
    	m=rd();
    	if(!W) return pt2::Solve(),0;
    	if(n<=11) return pt3::Solve(),0;
    	return pt1::Solve(),0;
    }
    
  • 相关阅读:
    AJAX聊天室小DEMO(讨厌JS,IE下有问题已解决)
    [ZT]线索二叉树(C#数据结构五)
    栈(C#数据结构学习二)
    eclipse 安装 resin 3 步骤
    解决全局utf8编码下asp.net接收gb2312乱码的问题
    模板里的控件要用FindControl(id)方法读取
    OpenSessionInView
    asp:button控件调用js函数不刷新方法
    OFFICE 出现“正在配置”的解决方法
    开发经验
  • 原文地址:https://www.cnblogs.com/chasedeath/p/12831098.html
Copyright © 2011-2022 走看看