zoukankan      html  css  js  c++  java
  • [JOI Open 2016] 摩天大楼

    一、题目

    点此看题

    二、解法

    这种绝对值求和可以当成一种模型来积累了,套路是微元贡献法(在 ( t CF) 的一道网络流题也出现过)

    我们先把权值离散化,对于离散化后的 (i<j)(|v_j-v_i|=sum_{k=i}^{j-1}v_{k+1}-v_{k}),那么 (v_{k+1}-v_k) 的贡献是满足 (ileq k,k+1leq j) 的对数。根据这个转化题意,我们按从小到大插入 (a) 构成序列,那么 (v_{k+1}-v_k) 的贡献次数就是前 (k) 个数构成连续段的端点个数,因为端点旁边以后一定会插入比 (k) 大的数,就会产生贡献,有点费用提前计算的感觉了。

    这提示我们可以使用连续段 (dp) 来解决这个问题,因为边界(端点是 (1/n))并不会产生贡献所以我们要把它单独记录,设 (f[i][j][k][d]) 表示插入了 (i) 个数,有 (j) 个连续段,贡献和是 (k),有 (d) 个端点的方案数,每次增量法考虑一个新数的插入,新的贡献和为 (k'=k+(v_{i+1}-v_i) imes(2j-d))

    • 作为一个新的连续段插入到不为边界的空隙中:(f[i+1][j+1][k'][d]leftarrow f[i][j][k][d] imes (j+1-d))
    • 合并两个连续段:(f[i+1][j-1][k'][d]leftarrow f[i][j][k][d] imes(j-1))
    • 添加到某个连续段非边界的端点处:(f[i+1][j][k'][d]leftarrow f[i][j][k][d] imes(2j-d))
    • 作为一个新的连续段,插到边界处:(f[i+1][j+1][k'][d+1]leftarrow f[i][j][k][d] imes (2-d))
    • 添加到某个连续段作为边界:(f[i+1][j][k'][d+1]leftarrow f[i][j][k][d] imes(2-d))

    时间复杂度 (O(n^2k)),最后答案显然是 (sum f[n][1][i][2]),但是要特判 (n=1)

    三、总结

    对于大小关系之类的限制(如绝对值),可以离散化之后取微元再贡献回去。

    连续段 (dp) 是解决序列计数问题的利器,学会主动使用它。

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int M = 105;
    const int N = 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,m,ans,a[M],f[M][M][N][3];
    void add(int &x,int y) {x=(x+y)%MOD;}
    signed main()
    {
    	n=read();m=read();
    	if(n==1) {puts("1");return 0;}
    	for(int i=1;i<=n;i++)
    		a[i]=read();
    	sort(a+1,a+1+n);
    	f[0][0][0][0]=1;
    	for(int i=0;i<n;i++) for(int j=0;j<=i;j++)
    		for(int k=0;k<=m;k++) for(int d=0;d<=2;d++)
    		{
    			int kk=k+(2*j-d)*(a[i+1]-a[i]),t=f[i][j][k][d];
    			if(kk>m || !t) continue;
    			//Case1:insert an seg without forming boundary
    			add(f[i+1][j+1][kk][d],t*(j+1-d));
    			//Case2:merge two seg
    			if(j) add(f[i+1][j-1][kk][d],t*(j-1));
    			//Case3:add to a seg
    			if(j) add(f[i+1][j][kk][d],t*(2*j-d));
    			//Case4:insert an seg,forming boundary
    			if(d<2) add(f[i+1][j+1][kk][d+1],t*(2-d));
    			//Case5:add to a seg,forming boundary
    			if(d<2 && j) add(f[i+1][j][kk][d+1],t*(2-d));
    		}
    	for(int i=0;i<=m;i++) add(ans,f[n][1][i][2]);
    	printf("%lld
    ",ans);
    }
    
  • 相关阅读:
    HDU2586 How far away?(tarjan的LCA)
    You Raise Me Up
    POJ2891 Strange Way to Express Integers(中国剩余定理)
    POJ2142 The Balance(扩展欧几里得)
    HDU 1166模仿大牛写的线段树
    NetWord Dinic
    HDU 1754 线段树裸题
    hdu1394 Minimum Inversion Number
    hdu2795 Billboard
    【完全版】线段树
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15216300.html
Copyright © 2011-2022 走看看