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);
    }
    
  • 相关阅读:
    storm 学习教程
    Scala 面向接口编程
    Scala 继承
    IntelliJ IDEA 代码检查规范QAPlug
    Spark入门实战系列
    IntelliJ Idea 常用快捷键 列表(实战终极总结!!!!)
    使用DOM解析XML文档
    栈结构Stack
    队列Queue ,双端队列Deque
    集合转换为数组toArray(),数组转换为集合asList()
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15216300.html
Copyright © 2011-2022 走看看