2024.8.3

今天状态爆炸了,有几次差点睡着。\(\_ + 20+60+0\)

挂分

\(\text{T4}\) 的答案初值设的 \(2147483647\),但是答案超 \(int\) 了,导致丢掉暴力状压分。

方便 \(\text{dp}\)\(\text{T4}\) 没写 \(\text{dp}\),没给 \(O(n^3)\) 分的 \(\text{T2}\) 却搞了好久 \(\text{dp}\)……

\(\text{T3}\)\(m \le 3\) 写挂了。

T1 怎么又是先增后减(why)

题意:把给定序列进行相邻两数交换,求存在整数 \(k \in [1,n]\),满足该序列前 \(k\) 个数单调不降,后 \(n-k\) 个数单调不增的最小操作次数。

考场在吃 shit,这种结论题是一点都对不上脑电波。调了快一个半小时还是假的思路,果断放弃了。

考虑到序列是单峰的,小数往边放,大数往中间放。那么每一个数可以放到左半边也可以放到右半边,选一个近的位置放就行了。我们考虑从小到大换数,从序列中最小的数 \(i\) 开始。它必然要换到序列的最左端或最右端。那么它分别需要和左右两边所有比它大的数全换一遍(如果有数和它一样便不用换了,直接转移到那个数),就能换到两边,取左右两边的最小步数计入答案。将它从序列中踢出去,因为它已经到位了。这样,不断找到序列中的最小数,按照上面的步骤换,答案就算完了。

总结一下我们的过程,就是在左右两边找到比它大的数的个数,取最小值计入答案。那么我们便可以不用从小到大找数,直接从 \(1\)\(n\) 扫一遍就行了。

接下来就是对于每一个 \(i\) ,怎么从 \(1\sim i-1\)\(i+1 \sim n\) 中找到比 \(i\) 大的个数,上个树状数组/平衡树/权值线段树乱搞就行了,\(O(n\log n)\)

const int N = 1e5+5;
int a[N];
mt19937 rnd(time(0));
struct TREE
{
	struct NODE
	{
		int l,r,val,siz,key;
	}tree[N<<2];
	int cnt{},root{}; 
	int newnode(int val){tree[++cnt].val = val; tree[cnt].siz =1,tree[cnt].key = rnd();	return cnt;}
	void pushup(int x){tree[x].siz = tree[tree[x].l].siz + tree[tree[x].r].siz + 1;}
	void split(int x,int val,int &l,int &r)
	{
		if(!x){ l = r = 0;return;}
		if(tree[x].val <= val){l = x;split(tree[x].r,val,tree[x].r,r);pushup(x);}
		else{r = x;split(tree[x].l,val,l,tree[x].l);pushup(x);}
	}
	int merge(int x,int y)
	{
		if(!x || !y) return x ^ y;
		if(tree[x].key > tree[y].key){tree[x].r = merge(tree[x].r,y);pushup(x);return x;}
		else{tree[y].l = merge(x,tree[y].l);pushup(y);return y;}
	}
	void ins(int val){int l,r;split(root,val,l,r);root = merge(l,merge(newnode(val),r));}
	int nxt(int val){int l,r,ans;split(root,val,l,r);ans = tree[r].siz,root = merge(l,r);return ans;}
}tree,tree2;
int minn[N];
signed main()
{
	#ifdef LOCAL
	freopen("in.in","r",stdin);
	#endif
	int n = read();
	for(int i{1};i<=n;i++) 
	{
		a[i]= read();tree.ins(a[i]);
		minn[i] = tree.nxt(a[i]);
	}
	int res{};
	for(int i{n};i>=1;i--)
	{
		tree2.ins(a[i]);
		minn[i] = min(minn[i],tree2.nxt(a[i]));
		res += minn[i];
	}
	writeln(res);
	return 0;
}

T2 美食节(festival)

题意:一个序列上有 \(N\) 个点,其中 \(\text{cjx}\) 最开始在 \(x\) 点,有 \(n\) 个区间按顺序给出, \(\text{cjx}\) 要依次进入这 \(n\) 个区间,定义花费为移动距离。\(\text{cjx}\) 每次都可以选择让 \(\text{wme}\) 帮他进入某次区间,花费不变但是他不必进入这个区间了。求这 \(n\) 个区间都被进入过后的最小花费。

显然要 \(\text{dp}\),诶就设 \(f_{i,j}\) 表示 \(\text{cjx}\) 在过第 \(i\) 个区间时在 \(j\) 这个位置的最小花费。一看数据范围:\(l,r \le 10^9\)。。那不急,\(n\le10^5\),那么关键点的数量必然不超过 \(2 \times 10^5+1\),离散化就行了。

那就设 \(f_{i,j}\) 表示 \(\text{cjx}\) 在过 \(i\) 个区间时在第 \(j\) 个关键点的最小花费。为什么是关键点,因为移动量小为优,确实只会在区间端点上停留。

他肯定有两种情况:

  • 自己走一段(或者不走)到第 \(i\) 个区间的端点处。
  • 自己走一段(或者不走),然后让 \(\text{wme}\) 帮他进区间。

显然的两种转移:

\[\left\{\begin{matrix} f_{i,j} =& \min(f_{i-1,s}+ dis(s,j),f_{i,j}) &j\in[L_i,R_i] \\ f_{i,j} =& \min(f_{i-1,s}+dis(s,j)+\min(dis(L_i,j),dis(R_i,j)),f_{i,j}) &\text{otherwise.} \end{matrix}\right. \]

先跑一遍,大样例跑出来了,但是 \(O(n^3)\) 跑了 \(106s\),想了想不知道怎么优化,就先跳了。

但是这个题给 \(O(n^3)\) 放的分跟 \(O(n!),O(2^n)\) 的分一样。

现在再想想怎么优化,考虑到转移方程里有一个 \(dis(s,j)\) 这个东西由 \(s\)\(j\) 两部分共同决定。由以前的经验,可以把它拆开预处理。因为 \(s\)\(i-1\) 的,那么我们每一次在算完 \(f_i\) 的时候就去更新一下当前的 \(j\) ,到 \(i+1\) 的时候就能用上了。

\(s\)\(j\) 左侧时,我们考虑怎么记录上一次的答案,能从上一次 \(O(1)\) 转移。因为有 \(dis\),我们就记录 \(f_{i-1,s}-pos_s\) ,因为答案是 \(f_{i-1,s} + pos_j -pos_s\),与 \(s\) 有关的正是 \(f_{i-1,s} - pos_s\)。同理,我们记录 \(f_{i-1,s}+pos_s\) 便可以在 \(s\)\(j\) 右侧的情况中用到。

当然还有 \(s = j\) 时。

那么我们原来要考虑整个关键点的转移,现在只要考虑三种类型的转移就行了,把一个 \(n\) 砍成了常数 \(3\)

注意转移的意义,从前面转移求的是前缀最小;从后面转移求的是后缀最小。我调的时候求了个全局最小……唐完了。

接下来就可以吃上 \(O(n^2)\)\(50\) 分了

const int N = 1e6+10;
int f[3][N];
int L[N],R[N];
int table[N];
int cnt{};
map<int,int>vis;
inline int aabs(int a)
{
	return a >= 0 ? a : -a;
}
int cost[3][N]; 
int now{};
void work()
{
	cost[1][cnt+1] = cost[0][0] = LONG_LONG_MAX;
	for(int j{1};j<=cnt;j++) cost[0][j] = min(cost[0][j-1],f[now][j] - table[j]);
	for(int j{cnt};j>=1;j--) cost[1][j] = min(cost[1][j+1],f[now][j] + table[j]);
}
signed main()
{
	#ifdef LOCAL
	freopen("in.in","r",stdin);
	#endif
	int n = read(),x = read();
	
	vector<int>temp;
	temp.push_back(x);
	for(int i{1};i<=n;i++)
	L[i] = read(),R[i] = read(),temp.push_back(L[i]),temp.push_back(R[i]);
	sort(temp.begin(),temp.end());
	int l = unique(temp.begin(),temp.end())-temp.begin();
	for(int i{1};i<=l;i++) table[++cnt] = temp.at(i-1),vis[temp.at(i-1)] = cnt;
	for(int i{1};i<=n;i++)
		L[i] = vis[L[i]],R[i] = vis[R[i]];
	memset(f,0x3f3f3f3f,sizeof(f));
	f[0][vis[x]] = 0;
	work();
	now = 1;
	for(int i{1};i<=n;i++)
	{
		for(int j{1};j<=cnt;j++) f[now][j] = LONG_LONG_MAX;
		for(int j{1};j<=cnt;j++)
		{
			if(j >= L[i] && j <= R[i])
				f[now][j] = min(f[now][j],f[now^1][j]),
				f[now][j] = min(f[now][j],cost[1][j]-table[j]),
				f[now][j] = min(f[now][j],cost[0][j]+table[j]);
			else 
				f[now][j] = min(f[now][j],f[now^1][j]+min(aabs(table[L[i]]-table[j]),aabs(table[R[i]]-table[j]))),
				f[now][j] = min(f[now][j],cost[1][j]-table[j]+min(aabs(table[L[i]]-table[j]),aabs(table[R[i]]-table[j]))),
				f[now][j] = min(f[now][j],cost[0][j]+table[j]+min(aabs(table[L[i]]-table[j]),aabs(table[R[i]]-table[j])));
		}
		work();
		now ^= 1;
	}
	now ^= 1;
	int res = LONG_LONG_MAX;
	for(int i{1};i<=cnt;i++) res = min(res,f[now][i]);
	writeln(res);
	return 0;
}

对于正解,先放张图。可以发现最优答案是一段区间,且在靠中部的位置,并不是一个单独的点。输出一些样例的 \(f_{n,j}\) 也可以发现这一点:如下图最后一行的答案 \(8\) 在中部且为一段区间。

所以我们要维护的是区间相等的位置以及他们的值。

开始时记 \(l = r = x,ans = 0\)。若下一个 \(L_i,R_i\) 包含它,不必修改;若包含了一部分,则不在 \(L_i,R_i\)内的点要增加答案,便不优,去掉这部分;若没有包含部分且 \(L_i > r\),那么所有值都要向右走,想想谁最优?越靠近 \(L_i\) 越优。所以 \([r,L_i]\) 为最优答案,\(ans + L_i-r\)\(l>R_i\) 同理,越靠近 \(R_i\) 走的越少,\([R_i,l]\) 为最优,答案 \(ans - R_i + l\)。然后就做完了。

做到最后原来是个贪心……

int ll{x},rr{x};
int res{};
for(int i{1};i<=n;i++)
{
    if(ll<=R[i] && L[i] <= rr) ll = max(ll,L[i]),rr = min(rr,R[i]);
    if (R[i] < ll) { 
        res += ll - R[i];
        rr = ll, ll = R[i];
    }
    if (L[i] > rr) {  
        res += L[i] - rr;
        ll = rr, rr = L[i]; 
    } 

}
writeln(res);

T3 环上合并

给一个环,环长为 \(n\),每次操作可以把 \(a_i\) 换成 \(a_{i-1},a_i,a_{i+1}\) 中最大值或最小值,对于每个 \(k(1\le k \le m)\) 求出,最少操作多少次能使所有数都等于 \(k\)

T4 送快递

\(n\) 个点,其中每个点的位置为 \(a_i\)(不保证 \(a_i\) 单调递增啊),现在有 \(\text{wymx}\)\(\text{wmw}\) 从初始点往 \(a_i\) 跳,跳到之后就要留在那个点,每一个点都要有人跳,求两人的最短跳跃路程和。

首先注意到有 \(n \le 20\)\(20\) 分,又想到这个题天然符合状压的 \(0-1\) 状态,直接状压,一遍过样例(忽略初始极大值赋小)。

接下来 \(n \le 1000\),有 \(50 \text{pts}\),给 \(O(n^2)\)\(\text{dp}\),但是状态不是很好设,所以我考场上甚至没有想到 \(\text{dp}\)。设 \(f_{i,j}\) 表示一个人到了 \(a_j\) 这个点,且另一个人在 \(a_i\) 这个点的最小总路程。

假设 \(\text{wmw}\) 到了 \(i-1\),下一个点如果 \(\text{wymx}\) 走,那么状态就将是 \(f_{i,i-1}\),而 \(\text{wymx}\) 可以从 \(i-1\) 后面的所有点走,即:\(f_{i,i-1}=\min\limits_{j=0}^{i-1} f_{j,i-1}+|a_i-a_j|\\\)。而如果 \(\text{wymx}\) 此时再向前进一步,就有 \(f_{i,j}=f_{i-1,j}+|a_i-a_{i-1}|\)

\[f_{i,i-1}=\min\limits_{j=0}^{i-1} f_{j,i-1}+|a_i-a_j|\\ f_{i,j}=f_{i-1,j}+|a_i-a_{i-1}| \]

posted @ 2024-09-14 21:21  WanGMiNgWeI  阅读(5)  评论(0编辑  收藏  举报