• [JZOJ5355] 【NOIP2017提高A组模拟9.9】保命


    题目

    描述

    在这里插入图片描述
    在这里插入图片描述
    题目已经足够清晰了,所以不再赘述题目大意。


    思考历程

    一眼看下去,好像是一道大水题!
    然而,再看几眼,感觉又不是一道水题!
    然后想了半天,感觉它特别难转移!
    最终打了一个暴力,然后发现样例没有过去!
    调试一波,发现原因是恶心的编号……(为什么要设置成这样,好不习惯啊……)
    最终交上去,5分!
    我的暴力不应该30分吗?
    欲哭无泪……


    正解

    这题的正解有一个很好的思想。
    首先,显然这题是DP,因为数据太大,不能用网络流,贪心显然是错误的。
    估计一下时间复杂度:O(NK)O(NK)
    如何做到优秀的转移?
    感觉上,如果正着转移,那就比较麻烦;但是我们能不能考虑反着转移呢?
    我们可以先将k=0k=0的答案计算出来,
    对于一段区间,就要减去它们之间的长度乘上区间左边的总数。
    因为题目要求最小,所以我们计算出这个东西的最大值,然后减去它。
    fi,kf_{i,k}表示现在到了ii这个点,放置了kk个消防栓的最大值。
    方程就出来了:fi,kfj,k1+Wj(DiDj)f_{i,k}leftarrow f_{j,k-1}+W_j(D_i-D_j)
    其中WiW_i表示ii之前(含)的总数,DiD_i表示从起点到ii的距离。
    注意在实现的过程中的细节,什么加一减一之类的东西(调试时最可恶的东西就是这个了)。
    这是一个O(N2K)O(N^2K)的做法,还是不够优秀。
    再仔细观察一下式子,我们发现好像可以斜率优化!
    然后斜率优化一下,时间复杂度就下降到O(NK)O(NK)
    斜率优化怎么搞?我就不打算讲了,都是那样推式子,自己推去。
    按道理来说这个时间其实是过得去的,可是数据坑爹,最后一个点特别恶心,卡常数都难卡过去。并且,这个数据本身就是错误的……所以说,如果你有了95分,恭喜你,实际上你已经AC了。

    题解上还有一些比较奇怪的做法。
    比如,当K=1K=1时,三分放在哪个点,得出最优解。
    如果K>1K>1呢?那就是神一般的三分套三分……不停套下去,一共KK层……
    这个方法不得不说特别强悍……
    也许是可以过的吧(说真的,我身边的同学没有一个人打这种奇葩做法)。

    还有一种做法叫作模拟退火。
    具体怎么做我就不说了,模拟退火的本质就是暴力……
    而且这题还有SPJ,所以模拟退火据说可以水到很多的分数……
    题解说期望97分……
    当然,如果能打正解,就尽量打正解。模拟退火就应该看成一个水分神器,在某些时候,据说模拟退火可以切爆正解!
    呜呜呜我不会模拟退火……


    代码

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 1000001
    int n,K;
    long long w[N+1],d[N+1],W[N+1],D[N+1];
    long long ans0;
    long long f[N+1][23];
    int pre[N+1][23];
    int q[N+1],head,tail;
    inline bool calc1(int i,int a,int b,int k){
    	return (f[a][k]-W[a]*D[a])-(f[b][k]-W[b]*D[b])<=D[i]*(W[b]-W[a]);
    }
    inline bool calc2(int a,int b,int c,int k){
    	return ((f[a][k]-W[a]*D[a])-(f[b][k]-W[b]*D[b]))*(W[c]-W[b])>=((f[b][k]-W[b]*D[b])-(f[c][k]-W[c]*D[c]))*(W[b]-W[a]);
    }
    void print(int i,int k){
    	if (!i)
    		return;
    	print(pre[i][k],k-1);
    	printf("%d ",i-1);
    }
    int main(){
    	freopen("life.in","r",stdin);
    	freopen("life.out","w",stdout);
    //	freopen("in.txt","r",stdin);
    	scanf("%d%d",&n,&K);
    	for (int i=1;i<=n;++i)
    		scanf("%d%d",&w[i],&d[i]);
    	long long sum=0;
    	for (int i=1;i<=n;++i){
    		sum+=w[i];
    		ans0+=sum*d[i];
    	}
    	W[0]=0,D[0]=0;
    	for (int i=1;i<=n+1;++i)
    		W[i]=W[i-1]+w[i],D[i]=D[i-1]+d[i-1];
    	memset(f,128,sizeof f);
    	f[0][0]=0;
    	for (int k=1;k<=K+1;++k){
    		q[head=tail=0]=0;
    		for (int i=1;i<=n+1;++i){
    			while (head<tail && calc1(i,q[head],q[head+1],k-1))
    				++head;
    			f[i][k]=f[q[head]][k-1]+W[q[head]]*(D[i]-D[q[head]]);
    			pre[i][k]=q[head];
    			while (head<tail && calc2(q[tail-1],q[tail],i,k-1))
    				--tail;
    			q[++tail]=i;
    		}
    	}
    	printf("%lld
    ",ans0-f[n+1][K+1]);
    	print(pre[n+1][K+1],K);
    	return 0;
    }
    

    我认为这个程序不需要打注释……


    总结

    其实这题的斜率优化真的是一点也不难(我不会告诉我在NOIP2018前一个星期才学会了斜率优化)。
    这题最终要的地方是从反面求答案。
    有时候反面求比正面求简单多了。

  • 相关阅读:
    Django
    MySql、Mongodb和Redis的区别
    数据库读写分离之配置Django实现数据库读写分离
    MySQL数据库优化
    01--web框架本质 wsgiref模块介绍
    CI上传图片 The filetype you are attempting to upload is not allowed.
    微信小程序 swiper和video的autoplay设置冲突问题
    关于手机端页面使用border-radius:50%不能使用div变为圆形问题
    微信小程序支付获取params的时候使用JsApiPay失败
    小程序使用笔记
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145245.html
Copyright © 2020-2023  润新知