zoukankan      html  css  js  c++  java
  • 6439. 【GDOI2020模拟01.17】小 ω 数排列

    题目

    正解

    一种很套路的笛卡尔树DP……
    看着那个绝对值很烦,于是我们考虑一种全新的转移方式。
    考虑把数字从小到大,一个一个插入当前序列的空隙中。
    于是我们就可以知道这个数字对答案的贡献。
    比如,如果当前它两边没有数字,那么系数就是(-2)
    如果一边有数字,系数就是(+1-1)也就是(0)
    如果两边都有数字,系数就是(2)
    当然,对于放在边界的数字要特殊判断。
    于是就有了这样的DP:设(f_{i,j,k,t})表示前(i)个数都放好了,形成(j)段序列,当前的总贡献是(k),放了(t)(取值为(0,1,2))个在边界上的·数字。
    讨论一下这个数要插入到哪里,有没有跟相邻的区间相连。这样就可以搞出一个DP。
    写出所有方程式,这时候会发现(k)的取值范围比较奇怪,必定存在着许多的冗余状态。
    设一个(k)的替代品(k')(k'=k+(2j-t)a_i)
    相当于我们想象一下在所有的空位填上一个(a_i)得到的总贡献。
    有这个总贡献的定义得知,(k')是在一个比较正常的取值范围之内的。
    枚举的时候枚举(k'),再转化成(k),存储的时候转化成(k')
    这样就可以保证时间复杂度了。


    代码

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 110
    #define maxL 1010
    #define mo 1000000007
    #define ll long long
    int n,L;
    int a[N];
    int f[N][N][maxL][3];
    void upd(int i,int j,int k,int t,int val){
    //	printf("f(%d,%d,%d,%d)+=%d
    ",i,j,k,t,val);
    	k=k+(2*j-t)*a[i];
    	if (k<=L)
    		(f[i][j][k][t]+=val)%=mo;
    }
    int main(){
    	freopen("count.in","r",stdin);
    	freopen("count.out","w",stdout);
    //	freopen("in.txt","r",stdin);
    //	freopen("out.txt","w",stdout);
    	scanf("%d%d",&n,&L);
    	if (n==1){
    		printf("1
    ");
    		return 0;
    	}
    	for (int i=1;i<=n;++i)
    		scanf("%d",&a[i]);
    	sort(a+1,a+n+1);
    	memset(f,0,sizeof f);
    	f[0][0][0][0]=1;
    	ll ans=0;
    	for (int i=0;i<n;++i){
    		for (int j=0;j<=i;++j)
    			for (int k=0;k<=L;++k){
    				for (int t=0;t<=2;++t){
    					int tmp=f[i][j][k][t];
    					if (!tmp)
    						continue;
    					int rk=k-(2*j-t)*a[i];
    //					printf("
    f(%d,%d,%d,%d)=%d
    ",i,j,rk,t,f[i][j][k][t]);
    //					if (i==2 && j==2 && rk==-3 && t==2)
    //						printf("");
    					upd(i+1,j+1,rk-2*a[i+1],t,(ll)tmp*(j+1-t)%mo);
    					if (j)
    						upd(i+1,j,rk,t,(ll)tmp*(2*j-t)%mo);	
    					if (t<2){
    						upd(i+1,j+1,rk-a[i+1],t+1,(ll)tmp*(2-t)%mo);
    						if (j)
    							upd(i+1,j,rk+a[i+1],t+1,(ll)tmp*(2-t)%mo);
    					}
    					if (j>=2)
    						upd(i+1,j-1,rk+2*a[i+1],t,(ll)tmp*(j-1)%mo);
    				}
    			}
    	}
    //	printf("
    ");
    	for (int k=0;k<=L;++k){
    //		printf("f(%d,%d,%d,%d)=%d
    ",n,1,k,2,f[n][1][k][2]);
    		ans+=f[n][1][k][2];
    	}
    	printf("%lld
    ",ans%mo);
    	return 0;
    }
    

    总结

    笛卡尔树DP,也就是按顺序枚举插入,在转移过程中合并连续段。这是一个大套路啊……

  • 相关阅读:
    P2486 [SDOI2011]染色 (树链剖分)
    机房测试:Dove打扑克(vector暴力)
    机房测试:sort(归并+概率期望dp)
    区间覆盖问题总结(贪心)
    机房测试:停不下来的团长奥加尔(dp)
    博客目录
    团队作业week16
    Beta阶段项目展示
    Beta阶段项目终审报告
    Beta阶段测试报告
  • 原文地址:https://www.cnblogs.com/jz-597/p/12238823.html
Copyright © 2011-2022 走看看