• 【题解】(我出的题)XM捡面筋


    (XM) 捡面筋

    ————感谢XMZ贡献的名誉题

    题目背景

    众所周知,(XM) 是一个爱吃面筋的孩子。

    题目描述

    lcez的面筋生产部有(n)栋楼排成一列,每栋楼有(h_i)层(从第(1)层开始算,第(h_i)层是楼顶),第(i)栋楼的每层都有价值为(v_i)的面筋。而(XM) 就在楼之间捡面筋。

    (XM) 可以花费一单位时间向上或向下一层。当然,为了展示他高超的跳楼技巧,他可以从一个楼的某一层跳到相邻楼的同层,如果相邻楼没有当前楼高,(XM) 就会跳到相邻楼的楼顶。他在相邻两栋楼之间跳跃一次花费的时间也是一个单位时间。

    特别地,因为每栋楼的楼顶没有围栏,当(XM) 在楼顶时,爱装*的(XM) 可以一下子跳到本栋楼的一楼,而只花费一个单位时间。(XM) 捡面筋不需要花费时间。

    (XM) 现在站在第1栋楼的楼顶津津有味地吃着楼顶的面筋,问题是还有(m)个单位时间(XM) 就要回去打(WZ)了,他想知道自己最多能得到多少价值的面筋。

    输入格式

    第一行两个正整数(n)(m)

    下面(n)行每行两个正整数(h_i)(v_i)

    输出格式

    一个正整数,表示(XM) 能获得的面筋价值最大值

    样例输入

    3 3
    2 1
    1 5
    3 4
    

    样例输出

    14
    

    数据规模及约定

    //喂喂喂,别忘了开long long

    对于(10)%的数据,(v_i) = 1 (聪明的你就知道该输出什么了

    对于另外(10)%的数据,n = 10

    对于另外(20)%的数据,(n <= 100)

    对于另外(20)%的数据,(n<=1000)

    对于另外(20)%的数据,(n <= 10000)

    对于(100)%的数据,(n,m <= 100000) , (h_i,v_i <= 2000)

    题解

    问题的实质

    1)环

    每栋楼其实是一个环。不管是从哪一层到达当前楼,花费多长时间,就能得到多少面筋。(忽略从旁边楼过来的时间,我们只看在楼内部花费的时间)。也就是说楼可以任意分割。

    知道每栋楼是一个环,这很重要。

    2)简单背包

    那么这其实是一个背包问题,并且由于楼可以分割,这个问题就变成了最简单的贪心买东西问题:

    按性价比由高到低排序的背包问题。

    不理解吗?就是这个

    求解并得分

    而且我的数据好像不用开(longlong)也行QAQ

    在楼间跳跃,跳到下一栋再跳回来?聪明的(XM)肯定不会这么做。

    我们不确定最后一单位时间(XM)在哪一栋楼,所以最外层枚举结束的楼。

    确定了最后一栋楼是第(i)栋,现在我们要做的就是将前(i)栋楼按(v)排序,然后在(m)允许范围内把(v)最大的整栋楼的面筋捡光。当剩余时间不允许捡光整栋楼的时候,能捡多少捡多少(分割物品)。

    具体实现时,可以用(sort)实现前(i)栋楼的有序排列,也可以用priority_queue​

    需要注意的是,(XM)是在第(1 - i)栋楼之间连续跳跃,所以这(i)栋楼每一栋都要至少花费1个单位时间(不管他的v是多少)(第一栋楼不需要花费1单位时间)。

    对于循环的每一个(i),我们得到一个(ans_i),最后的(ans = max(ans_i))

    code#1

    复杂度分析:(O(n^2 log n)),只能应对(1000)以内的数据

    #include<cmath>
    #include<queue>
    #include<cstdio>
    #include<string>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    const int maxn = 100010;
    int n, m, tim, usedti;
    long long ans, ans0;
    struct build{
    	int h, v, id;
    }bd[maxn]; 
    bool operator < (build a, build b){
    	return a.v < b.v;
    }
    priority_queue<build> q;
    /*
    bool cmp(build a, build b){
    	return a.v > b.v;
    }*/
    int main(){
    	freopen("gluten10.in","r",stdin);
    	freopen("gluten10.out","w",stdout);
    	scanf("%d%d",&n,&m);
    	for(int i = 1; i <= n; i++){
    		scanf("%d%d",&bd[i].h,&bd[i].v);
    		bd[i].id = i;
    		bd[i].h --;
    	}
    	for(int end = 1; end <= n; end++){
    		for(int i = 1; i <= end; i++)	q.push(bd[i]);
    		ans0 = 0;
    		tim = m - end + 1;
    		usedti = 0;
    		for(int i = 1; i <= end; i++)		ans0 += bd[i].v;
    //		sort(bd + 1, bd + 1 + end, cmp);
    		for(int i = 1; i <= end; i++){
    			int id = q.top().id;
    			q.pop();
    			if(usedti + bd[id].h <= tim){
    				ans0 += bd[id].h * bd[id].v;
    				usedti += bd[id].h; 
    			}		
    			else{
    				ans0 += bd[id].v * (tim - usedti);
    				usedti = tim;
    			} 
    		}
    //		cout << end << " " << ans0 << endl;
    		ans = max(ans, ans0); 
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    正解

    上述代码之所以是(n^2logn)级别,是因为对于每一次循环,我们都用(O(n))的复杂度将(1-end)入队,再用(O(n))计算每一个物品的价值贡献。

    正解是这样的:

    维护一个小根堆(q),堆内元素是楼(结构体build),维护堆内所有楼的面筋总价值和sumv,维护堆内所有楼的层数和sumh(也就是捡完全部楼所要花费的时间)。

    每次循环都将当前这栋楼扔小根堆。当(sumh) 大于我们能够自由支配的时间(设为(m0))时,从小根堆里不断地弹出一些(v)值很小的楼。弹出的时候(sumh)(sumv)随着减小。如果弹出当前堆内(v)最小的楼之后(sumh) 要小于我们能自由支配的时间(m0),停止弹出,此时(sumh)刚好大于(m0)

    然后计算价值。注意到计算价值时有两种情况。

    1)如果一开始(sumh)就小于(m0),也就是说没有从对内弹出任何元素,那么答案就是(sumv)加上经过所有楼一次的时间。

    2)否则。当前队内的第一个元素(也就是(v)最小的那栋楼)我们分割它,其余队内的楼我们全捡一遍。

    code#2

    #include<cstdio>
    #include<cstdlib>
    #include<map>
    #include<cmath>
    #include<ctime>
    #include<queue>
    #include<string>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define ll long long
    #define ci cosnt int &
    using namespace std;
    const int maxn = 1000010;
    int n, m, h, v;
    ll ans, passgot, sumv, sumh;
    struct build{
    	int h, v;
    	build(){}
    	build(int h0, int v0) : h(h0), v(v0){};
    };
    bool operator < (build a, build b){
    	return a.v > b.v;
    }
    priority_queue <build> q; 
    int main(){
    
    	scanf("%d%d",&n,&m);
    	m++;
    	for(int i = 1; i <= n; i++){
    		scanf("%d%d",&h, &v);
    		m--;
    		passgot += v;
    		q.push(build(h-1, v));
    		sumh += h-1;
    		sumv += 1ll*v*(h-1);		
    		while(q.size() && sumh - q.top().h >= m){
    			sumh -= q.top().h;
    			
    			sumv -= q.top().v * q.top().h * 1ll;
    			q.pop();
    		}
    /*		if(sumh >= m){
    			ans = max(ans, sumv - (sumh - m) * q.top().v + passgot);
    			cout << "*" << sumv << endl;
    		}
    		else ans = sumv + passgot;
    		cout << ans << endl;
    */
    		ans = max(ans, sumh >= m ? sumv - (sumh - m) * q.top().v + passgot : sumv + passgot);
    		if(m <= 0)	break;
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    
    

    code#3

    事实上,手写堆的常数比STL堆的常数小很多,可以跑(10^6)的数据

    #include<bits/stdc++.h>
    const int S=1<<20;
    char frd[S],*ihead=frd+S;
    const char *itail=ihead;
    
    inline char nxtChar()
    {
    	if(ihead==itail)
    	    fread(frd,1,S,stdin),ihead=frd;
    	return *ihead++;
    }
    
    template<class T>
    inline void read(T&res)
    {
    	char ch;
    	while(ch=nxtChar(),! isdigit(ch));
    	res=ch^48;
    	while(ch=nxtChar(),isdigit(ch))
    	    res=res*10+ch-48;
    }
    
    typedef long long ll;
    const int N=1e6+5;
    int n;
    ll m,tot_h,tot_v,ans;
    template<class T>
    inline void CkMax(T & x,T y){if(x<y) x=y;}
    
    struct point
    {
    	int v,h;
    	point(){}
    	point(int V,int H):
    	    v(V),h(H){}
    	
    	inline bool operator<(const point & a)const
    	{
    		return v<a.v;
    	}
    };
    
    struct Heap
    {
    	point g[N];
    	int len;
    	
    	inline void Push(const point & res)
    	{
    		int now=++len,nxt=len>>1;
    		while(nxt)
    		{
    			if(res<g[nxt])
    			    g[now]=g[nxt],now=nxt,nxt>>=1;
    			else break;
    		}
    		g[now]=res;
    	}
    	inline void Pop()
    	{
    		int now=1,nxt=2;
    		point res=g[len--];
    		while(nxt<=len)
    		{
    			if(nxt<len&&g[nxt|1]<g[nxt]) nxt|= 1;
    			if(g[nxt]<res)
    			    g[now]=g[nxt],now=nxt,nxt<<=1;
    			else break;
    		}
    		g[now]=res;
    	}
    }Q;
    
    int main()
    {
    	read(n);read(m);++m;
    	ll res=0;
    	for(int i=1,h,v;i<=n;++i)
    	{
    		read(h);read(v);
    		--m;
    		res+=v;
    		if(!m) break;
    		if(h>1)
    		{
    			point tmp=point(v,h-1);
    			Q.Push(tmp);
    			tot_h+=tmp.h;
    			tot_v+=1ll*tmp.h*tmp.v;
    			while(Q.len&&tot_h-Q.g[1].h>=m)
    			{
    				tot_h-=Q.g[1].h;
    				tot_v-=1ll*Q.g[1].h*Q.g[1].v;
    				Q.Pop();
    			}
    		}
    	    CkMax(ans,tot_h>=m?
    	        res+tot_v-1ll*Q.g[1].h*Q.g[1].v+1ll*(m-tot_h+Q.g[1].h)*Q.g[1].v:res+tot_v);
    	}
    	std::cout<<ans<<std::endl;
    	return 0;
    }
    

    本地测,当(n = 1e6, m = 1e6, h_i <= 1000, v_i <= 1000)时,手写堆跑了(147ms),STL的优先队列跑了(1433ms),大概是(10)倍的差距。所以建议学一下手写堆。

    Thanks

  • 相关阅读:
    一个网站需求说明书的示例
    产品设计与PRD介绍
    研发效能度量案例
    项目管理过程流程图
    变量 $cfg['TempDir'] (./tmp/)无法访问。phpMyAdmin无法缓存模板文件,所以会运行缓慢。
    wordpress函数大全列表整理
    PCLZIP_ERR_BAD_FORMAT (-10) : Unable to find End of Central Dir Record signature
    通过写脚本的方式自动获取JVM内的进程堆栈信息等内容
    简单定位占用最高CPU的java进程信息
    使用linux上面powershell安装vm powercli 连接vcenter 通过计划任务自动创建部分虚拟机的快照以及自动清理过期快照的办法
  • 原文地址:https://www.cnblogs.com/ZhengkunJia/p/13325354.html
Copyright © 2020-2023  润新知