• 【noip模拟】D(==)


    Portal --> who knows ==

    Description

      数轴上面有一些洞,有一些老鼠,每个洞有一个容量限制,一只位于(x)的老鼠进到位于(y)的洞要花费(|x-y|)的代价,问所有老鼠都进洞的最小代价,如果没有合法方案输出(-1)

      数据范围:(n,m<=10^6,1<=c_i<=n),其中(c_i)表示每个洞的容量,(0<=)位置(<=10^9)

      

    Solution

      这题的话。。长得像一个dp。。但是如果直接莽显然是不行的。。

    ​  注意到一个点:将洞和老鼠都按照位置排序,最优方案中进入同一个洞的老鼠一定是一段连续的区间,那么记(f[i][j])表示前(i)个洞,前(j)只老鼠已经进洞了的最小代价,转移的话:

    [f[i][j]=min(f[i-1][k]+sum[j]-sum[k]) ]

      其中(k)的枚举范围是([j-c[i],j])(sum[j])表示的是(sumlimits_{p=1}^j|rat_p-hole_i|)

    ​  那么线段树优化一下就有一个(O(nmlogn))的做法(然而实际上好像直接单调栈什么的搞一搞除去排序就是(O(nm))了)

    ​   

      然后注意到这个dp是没有前途的。。(没有办法继续优化了),所以换一种思路

      不选择分开考虑,而是选择将所有的老鼠和洞放在一起,这里有一种很妙的用堆的做法(疯狂orzhwc),用两个堆分别维护老鼠和洞

      我们将老鼠和洞放在一条线上,一只老鼠要么会被丢到前面,要么会被丢到后面,那么我们从左往右扫,考虑如果扫到一只老鼠,我们先无脑将它丢到它前面的离它最近的有容量的洞里面,更优情况的替换我们放在扫到一个洞的时候处理

      扫到一个洞的时候,如果说有只当前被丢到前面洞里的老鼠丢到这个洞里面会比当前更优,那么就把这只老鼠丢进来,更新当前答案,具体的判断方式是:我们将前面的那个洞的位置以当前老鼠为对称轴对称过来,如果说对称过来得到的位置比当前洞的位置更大,那么说明当前洞更优

      具体处理的话就是用一个堆维护被丢进前面的洞里的老鼠对应的洞的对称值(也就是(rat+hole),其中(rat)表示这只老鼠的位置,(hole)表示对应洞的位置),然后如果堆顶的那个老鼠不能被当前这个洞更新,那肯定也不能被后面的洞更新了,所以直接弹掉

      然而这里有一个问题,当前的决策放在后面不一定是最优的,也就是说有的老鼠当前可能丢到后面比较优,但是放在全局可能就是丢到前面比较优了,为了应对这种情况,我们需要一个撤销操作,具体的实现就是,如果说在扫到一个洞的时候,我们用这个洞更新了某只老鼠,那么对应的我们要多加一个相当于撤销操作的洞到洞的堆里面,撤销的具体含义就是:如果说当前的老鼠选了一个撤销洞,那么就相当于令被撤销洞更新的老鼠重新回到更新前的洞里,并且将当前的老鼠丢到这个洞里

    ​  实现上的话,假设撤销洞位置为(y),被撤销洞更新的老鼠(A)在更新前在的洞是(x)(delta)是该老鼠选(y)比选(x)优多少,那么撤销洞的位置就应该为(y+delta),并且容量为(1),之所以这么设是因为:如果说当前的老鼠(B)选了这个洞,那么(ans)会被加上(rat_B-(y+delta)),也就是相当于将(delta)去掉(老鼠(A)回到(x)),然后再加上老鼠(B)(y)这个洞的贡献

      然后一路扫过去就好了ovo

      

      mark:撤销洞的思路很有意思,mark一下

      

      代码大概长这个样子

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #include<algorithm>
    #define ll long long
    #define Pr pair<ll,int>
    #define mp make_pair
    using namespace std;
    const int N=1000010;
    const ll inf=1LL<<60;
    struct Data{
    	int p,c,ty;
    	friend bool operator < (Data x,Data y){
    		return x.p==y.p?x.ty<y.ty:x.p<y.p;
    	}
    }a[N*2];
    priority_queue<Pr> hole;
    priority_queue<ll> rat;
    int n,m,cnt;
    ll ans;
    void solve_rat(int i){
    	ll w=inf;
    	Pr tmp;
    	if (!hole.empty()){
    		tmp=hole.top(); hole.pop();
    		w=a[i].p-tmp.first;
    		--tmp.second;
    		if (tmp.second)
    			hole.push(tmp);
    	}
    	rat.push(w+a[i].p);
    	ans+=w;
    }
    void solve_hole(int i){
    	ll delta;
    	while (a[i].c&&!rat.empty()&&a[i].p<rat.top()){
    		delta=a[i].p-rat.top(); rat.pop();
    		ans+=delta;
    		--a[i].c;
    		hole.push(mp(a[i].p+delta,1));
    	}
    	if (a[i].c)
    		hole.push(mp(a[i].p,a[i].c));
    }
    void solve(){
    	for (int i=1;i<=cnt;++i)
    		if (a[i].ty==0)
    			solve_rat(i);
    		else
    			solve_hole(i);
    	printf("%lld
    ",ans);
    }
    void print(Pr x){printf("(%d,%d)
    ",x.first,x.second);}
    
    int main(){
    #ifndef ONLINE_JUDGE
    	freopen("a.in","r",stdin);
    #endif
    	int x,y;
    	ll sum=0;
    	scanf("%d%d",&n,&m);
    	cnt=0;
    	for (int i=1;i<=n;++i) scanf("%d",&a[++cnt].p),a[cnt].ty=0;
    	for (int i=1;i<=m;++i){
    		scanf("%d%d",&x,&y);
    		if (!y) continue;
    		a[++cnt].p=x; a[cnt].c=y; a[cnt].ty=1;
    		sum+=y;
    	}
    	if (sum<n){printf("-1
    "); return 0;}
    	sort(a+1,a+1+cnt);
    	solve();
    }
    
  • 相关阅读:
    C# 随机生成姓名的方法
    Task 异步编程测试案例及基础应用说明
    C# 多线程 Parallel.For 和 For 谁的效率高?那么 Parallel.ForEach 和 ForEach 呢?
    C# SignalR 即时通信
    C#中out和ref之间的区别
    LInq之Take Skip TakeWhile SkipWhile Reverse Union Concat 用法
    C# LINQ 详解 From Where Select Group Into OrderBy Let Join
    JS 数组去重的几个方法
    attachEvent和addEventListener区别
    Event事件跨浏览器封装
  • 原文地址:https://www.cnblogs.com/yoyoball/p/9885404.html
Copyright © 2020-2023  润新知