其他分享
首页 > 其他分享> > CF1051G 题解

CF1051G 题解

作者:互联网

Link,Div2,2900)

考场上看到这道题:哇!这个操作好神奇!哇!样例这个操作方案太妙了!卧槽?为什么总费用还能是负数?(花30min模拟样例)什么阴间操作,毫无规律可循,跑路跑路。……

笔者赛场上的思维能力仅限于此。言归正传,提升观察的高度,发现每个 \(a_i\) 操作前或操作后总有一个 \(a_j\) 与之相邻,于是往 \(a\) 值域上的连续段上去想。设初始的 \(a\) 序列为 \(p\),下文的 \(a\) 均指若干次操作后得到的 \(a\)。跳出出题人挖的思维陷阱,从结果出发,先上两个很重要的 Lemma:

1.每加入一个 \(p_i\) 后经操作得到的满足要求(两两不同)的 \(a_1,\cdots,a_{i}\) 组成的集合是确定的,不确定性仅在于如何分配这个集合中的 \(i\) 个数到 \(1,\cdots,i\) 中去;

2.答案与操作过程无关而仅与最终的序列 \(a\) 有关,对于一种 \(a\) 我们的答案为 \(\sum_{j=1}^i (a_j-p_j)\times b_j=\sum_{j=1}^ia_jb_j-\sum_{j=1}^i p_jb_j\)。

第二个较为浅显,一次让 \(a_j\gets a_j+1\) 的操作花费为 \(b_j\),让 \(a_j\gets a_j-1\) 的操作花费为 \(-b_j\),所以对于这个 \(j\),从 \(p_j\) 转移到 \(a_j\) 的费用便一定是 \((a_j-p_j)\times b_j\)。

对于第一个,数学归纳。\(i=1\) 时显然;若 \(a_1,\cdots,a_{i-1}(i>1)\) 组成的集合确定,设其为 \(S_{i-1}\),将其排序后形成的连续段为 \([l_1,r_1],\cdots,[l_s,r_s](r_k<l_{k+1})\),分情况讨论:

以上所有讨论都是在忽略费用的情况下进行的,却能够使接下来的分析轻松许多。我们加进 \(b_i\) 的限制看看会发生什么。

上面我们分析了“可行性”,现在我们来谈“最优性”。我们已经把讨论范围缩减到了一个一个一个一个的连续段,连续段们的操作相互独立;由 Lemma2 我们完全不用管操作过程而只注重结果(@JustKevinXie(现名Destroyer_Garing));由 Lemma1 无论怎么操作得到的最终 \(a_{1,\cdots,i}\) 集合确定。这启发我们对每一个连续段进行乱搞(我们也只能对每个连续段分别乱搞),把它翻个底朝天都没问题,只需要达到最优费用即可。重新看向 Lemma2 的式子:

\[\sum_{j=1}^i a_jb_j-\sum_{j=1}^ip_jb_j \]

后面那项确定,我们只需最小化 \(\sum_{j=1}^i a_jb_j\)。对于每一个连续段我们可以随便镐镐鑐鑐,准确来说就是我们可以任意改变一个连续段中 \(a_i\) 的排列,即若一个连续段(其中数两两不同)是 \(a_{q_1},a_{q_2},\cdots,a_{q_s}\) 经操作得出的结果的集合,那么这个连续段中的数可以任意分配给 \(a_{q_1},\cdots,a_{q_s}\)。问题来了,如何分配才能使得 \(\sum a_jb_j\) 最小呢?

这是个经典问题,我们会发现重排后最小的 \(b_j\) 对应最大的 \(a_j\)、次小的 \(b_j\) 对应次大的 \(a_j\)、…… 、最大的 \(b_j\) 对应最小的 \(a_j\) 一定是最优方案。证明也很简单,随便交换两个 \(a_j\) 都只会使答案变劣。

于是这道题做完了。我们要做的便是维护每个连续段(可以用 std::set 或者各种奇怪的东西),对每个连续段分别开一个平衡树/动态开点线段树维护单调的 \(b_j\),加入 \(p_i\) 时直接按照我们证明 Lemma1 时给出的操作方案不断让 \(a_i\gets a_i+1\) 然后分三种情况讨论、加上贡献即可,合并连续段时用启发式合并。总复杂度 \(O(n\log^2 n)\)。

参考实现:

#include <cstdio>
#include <random>
#include <vector>
#include <set>
#define ll long long

using namespace std;

namespace fIO{
    bool f;char c;void re(int &x){
        x=0;f=0;c=getchar();
        while(c<'0'||c>'9') f|=c=='-',c=getchar();
        while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
        if(f) x=-x;
    }
    void re(char &c){
        c=getchar();while(c==' '||c=='\n'||c=='\r') c=getchar();
    }
    template<class tp,class ...Tp>void re(tp &x,Tp &...y){re(x);re(y...);}
    char st[20];int tp;
    void wr(int n){
        if(!n) return putchar('0'),void();
        if(n<0) putchar('-'),n=-n;
        while(n) st[++tp]=n%10+48,n/=10;
        while(tp) putchar(st[tp--]);
    }
    inline void wr(char c){putchar(c);}
    template<class tp,class ...Tp>void wr(tp x,Tp ...y){wr(x);wr(y...);}
}using fIO::re;using fIO::wr;

mt19937 rnd;

const int N=4e5+5.14114;

int memo[N],tp;

struct info{
	int c;ll s;
	info operator+(const info &x)const{
		return (info){c+x.c,s+x.s};
	}
};

vector<int> vals;

namespace BLT{
	struct node{
		int v,ls,rs;unsigned int pr;info s;
		#define v(x) t[x].v
		#define ls(x) t[x].ls
		#define rs(x) t[x].rs
		#define pr(x) t[x].pr
		#define s(x) t[x].s
	}t[N];
	void updata(int x){s(x)=s(ls(x))+s(rs(x))+(info){1,v(x)};};
	void div(int now,int &x,int &y,int k){
		if(!now) return x=y=0,void();
		if(v(now)<=k) x=now,div(rs(now),rs(now),y,k);
		else y=now,div(ls(now),x,ls(now),k);
		updata(now);
	}
	int merge(int x,int y){
		if(!x||!y) return x|y;
		if(pr(x)<pr(y)){
			rs(x)=merge(rs(x),y);
			updata(x);return x;
		}else{
			ls(y)=merge(x,ls(y));
			updata(y);return y;
		}
	}
	int newnod(int k){
		int nwn=memo[tp--];t[nwn]=(node){k,0,0,rnd(),(info){1,k}};return nwn;
	}
	void ins(int &rt,int k){
		int x,y;div(rt,x,y,k);
		rt=merge(merge(x,newnod(k)),y);
	}
	info qryles(int &rt,int k){
		int x,y;div(rt,x,y,k-1);info res=s(x);
		rt=merge(x,y);return res;
	}
	info qrygrt(int &rt,int k){
		int x,y;div(rt,x,y,k);info res=s(y);
		rt=merge(x,y);return res;
	}
	void breakdown(int now){
		if(!now) return ;
		breakdown(rs(now));
		vals.push_back(v(now));
		breakdown(ls(now));
		memo[++tp]=now;
	}
}using namespace BLT;

struct itv{
	int l,r,rttr;
	bool operator<(const itv &x)const{
		return l<x.l;
	}
};

set<itv> sa;

ll saftz;

enum position{lft,rht};

set<itv>::iterator addtoitv(set<itv>::iterator p,int b,position po){
	int l=p->l,r=p->r,rttr=p->rttr;
	if(po==lft){
		--l;info x=BLT::qrygrt(rttr,b);
		saftz-=x.s-1ll*(l+x.c)*b;
	}else{
		++r;info x=BLT::qryles(rttr,b);
		saftz+=x.s+1ll*(r-x.c)*b;
	}
	BLT::ins(rttr,b);sa.erase(p);
	return sa.insert((itv){l,r,rttr}).first;
}

int main()
{
	while(tp<N) memo[tp]=N-tp,++tp;--tp;
	int n;re(n);ll sorgn=0;
	for(int i=1;i<=n;++i){
		int a,b,flg=0;re(a,b);sorgn+=1ll*a*b;
		auto py=sa.upper_bound((itv){a});
		auto px=py;if(px!=sa.begin()) --px;
		if(py!=sa.begin()&&px->r+1>=a) flg=1,a=px->r+1,px=addtoitv(px,b,rht);
		if(py!=sa.end()&&a==py->l-1){
			if(py!=sa.begin()&&a==px->r){
				vals.clear();
				int szx=px->r-px->l+1,szy=py->r-py->l+1,rttr;
				if(szx<szy){
					BLT::breakdown(px->rttr);
					int pl=px->l;sa.erase(px);
					for(int i=vals.size()-1;~i;--i) saftz-=1ll*vals[i]*(pl+i),
													py=addtoitv(py,vals[i],lft);
				}else{
					BLT::breakdown(py->rttr);
					int pl=py->l;sa.erase(py);
					for(int i=0;i<vals.size();++i) saftz-=1ll*vals[i]*(pl+i),
													px=addtoitv(px,vals[i],rht);
				}
			}
			else addtoitv(py,b,lft);
			flg=1;
		}
		if(!flg) sa.insert((itv){a,a,BLT::newnod(b)}),saftz+=1ll*a*b;
		printf("%lld\n",saftz-sorgn);
	}
}

所以说碰到这种题还是要提升观察高度、多从结果出发找共性,单单盯着样例看有时是不一定有结果的。

本题思维难度大码量也比较大(个人观点),反正我是不算快读写了130多行。很不理解同机房dalao是如何不压行的情况下写进60行以内的。

标签:rttr,int,题解,px,py,CF1051G,连续,操作
来源: https://www.cnblogs.com/vanspace/p/16547454.html