HZOI CSP-S模拟8——汤匙の爆零祭

血在前面:

《自爆家门》( "\(/\)" 后面的是后来改的):

捕获
最纯糖的一集,T1看出单调栈DP于是兴奋极了,抛弃单调栈,开始想DP,未果,寻病终, 后来这位唐人认为自己想到了一个比较真的做法,于是开始作法,努力搞了许久(这时已经有单调栈的雏形了,但这位\(螳臂\)依旧认为自己的逆天DP是正确的、真实的、可靠的),开始测大样例,发现疑似全过了(其实最后一个样例后面几位错了,但瞎子传奇之人眼对拍过了好久才反应过来),因做法逆天遂调试失败,交上去以为能对个一两个点,但假得很彻底T2一眼看出二分,看到神秘的题干描述又yy出了大根堆,但没把它俩往一块想,想到一个假的贪心,贪污腐败甲烷了;T3一看期望DP(快忘完了),再一看疑似高几高斯消元(泰坏辣,我不会),直接跳了T4一眼树剖,但人傻不会处理,且此时仅剩长达0.5h的时间,于是努力写了俩不知真假的电风扇,最终遗憾离场《这就是肝·螳臂·HZOI爆零王·对不起教教主·硬化·悲伤蛙一世的一上午》
然后就是非常感谢APJ学长不辞辛苦搬了这些模拟赛题,感觉思维难度和代码难度都有,都要好好想一想,但是我还是泰唐了,赛时全部想假了


开始题解:

T1 酒吧(bar)

题面大意:(其实并不很简洁明了)

一个长度为\(n\)的序列,选择第 \(i\) 个位置可以得到 \(p_i*c_i\) 的报酬,(其中 \(p_i\) 是你输入的,\(c_i=(next-last)\)\(last\) 是对于第 \(i\) 个位置上一次选择的位置, \(next\) 是下一个位置),使 \(∑c_i*p_i\) 最大化。


赛时错误作法做法(注:以下思路全是假的,只是作者想搞一篇类似“错题本”的博客才记录的错解,不要被误导,真要看正解的请移步“Solve”):

容易想到贪心/DP,于是可以开始乱搞。由于开头和结尾均选择一定最优,所以先将 \(dp_1\) 赋值成 \((n-1)*p_1\) ,记录一个上一次取的位置 \(last\) 以及该位置的值 \(lastp\) 和上上回取的位置 \(llast\) 以及该位置的值 \(llastp\) ,那么状态转移方程就是\(dp_i=max(dp_{last},dp_{last}-(n-i)*lastp+(n-last)*p_i,dp_{llast}-(n-i)*llastp+(n-llast)*p_i)\) ,时间复杂度 \(O(n)\)
于是我们就可以并非愉快地开始写了,写完可以惊奇地发现:最后一个大样例过不去。此时我们想:万一数据很蒻(比gyh还要蒻)呢?是不是能骗一点点分呢?

0.

捕获

骗我们的,这个做法是假的!学长的数据泰羟辣!!!


还记得《血在前面》中提到的单调栈吗?


Solve

容易经过努力地手玩样例可以发现,开头与结尾均选择一定是最优的。设选择的位置为 \(b_1,b_,…,b_m\) ,那么答案就是\(\sum_{i = 1}^{m-1} {(b_i+1-b_i)(p_{b_i}+p_{b_i+1})}\) 。发现这个其实与梯形面积公式很像,那么如果我们将所有 \((i,p_i)\) 全部看做二维平面上的点,所求贡献就是在这里面选若干个点,使得他们的连线与 \(x\) 轴形成的封闭图形的面积最大。那么显然选一个凸包是最优的,直接单调栈求得答案即可(说人话就是该柿子满足单调性,直接维护一个单调不减的单调栈就可以)。复杂度 \(O(n)\)


Code

#include <bits/stdc++.h>
using namespace std;
const int _ = 500010;
int n, s[_], t;
long long dp[_], p[_], ans;
int main(){
	freopen("bar.in", "r", stdin);
	freopen("bar.out", "w", stdout);
	scanf("%d", & n);
	for(int i = 1; i <= n; i ++){
		scanf("%lld", & p[i]);
	}
	dp[1] = (n - 1) * p[1];
	s[++ t] = 1;
	for(int i = 2; i <= n; i ++){
		while(t > 1 && p[i] * (n - s[t]) + dp[s[t]] - p[s[t]] * (n - i) < p[i] * (n - s[t - 1]) + dp[s[t - 1]] - p[s[t - 1]] * (n - i)){
			t --;
		}
		dp[i] = p[i] * (n - s[t]) + dp[s[t]] - p[s[t]] * (n - i);
		s[++ t] = i;
		ans = max(ans, dp[i]);
	}
	printf("%lld", ans);
	return 0;
} 

T2 逆转(ace)

题面大意:(其实像在说梦话)

一个长度为 \(n\) 的序列,第 \(i\) 个位置权值为 \(a_i\) ,选出一个长度为 \(k\) 的子序列,将子序列分成前后两段且不改变它们的编号顺序,将前后两段分别求它们的权值和,使最大权值和最小。


赛时错误做法:

首先动用人类智慧,结合多次做(挂)题(分)经验可以看出是二分答案,这时我们可以想到一个贪心:用一个结构体存下输入的所有数据以及它们的编号,按权值从小到大,编号从小到大 \(sort\) 一遍,再将前 \(k\) 个数存下来,并在第 \(k+1\)\(n\) 个数里面查找与第 \(k\) 个数相等的数,用一个变量 \(cnt\) 记录它们的位置,然后将前 \(k\) 个数按编号再 \(sort\) 一遍,处理前缀和 \(sum_1,sum_2,…,sum_k\)。二分答案时比较 \(sum_{mid}\)\(sum_{r}-sum_{mid}\) ,因为可能会有一些与第k大的数权值相同但相对于 \(mid\) 的位置不同的数,所以在二分里再套一个二分(因为赛时处理太过汤匙遂具体处理过程就不写了),如果相等就输出然后 \(break\) ,左边小于右边就让 \(l=mid+1\) ,反之就让 \(r=mid-1\)
之后我们就可以愉快地发现:前两个样例,过啦!!!1111

且慢!【尔康手】

-要不要看看第三个样例?
-好鸭好鸭(^ . ^)!
-你假了。
-欸?!
正确输出:
18809
我们的输出:
19032


为什么呢?

-让我们将第三个大样例 \(sort\) 一下并输出前 \(k\) 位:
2930 6324 9778 9555
-没问题吧,最小的最大值就是19032口牙。
-那我们再将前 \(k+1\) 位输出一下吧!
-嗯,嗯!
-2930 6324 9778 9555 10141
-欸, \(2930+6324+9555\) 好像确实等于 18809 呢!
-So,you will be like them abandon me. 我们又想假啦!


-怎么办呢喵?

还记得“血在前面”中提到的大根堆吗?


Solve

最大值最小类问题考虑二分答案,设当前二分的答案小于等于 \(x\) ,那么我们只需要知道是否存在选择方案使得和小于等\(x\) 且数量大于等于 \(k\) 。题目中要求划分成两段,那么我们考虑对每个前缀,和每个后缀求出可以选择的最多的数,那么如果存在一个分界点使得可以选择的最多的数大于等于 \(k\) 那么就是合法的。
考虑如何对每个前缀快速求出答案。首先肯定是贪心的从小到大依次选,直到总和超过 \(x\) 。那么考虑插入一个数后答案的变化,如果插入这个数之后总和不会超过 \(x\) ,那么可以直接插入,否则如果当前这个数小于已选的最大值,那么将最大值换成这个数显然更优,否则肯定是插入不了的。那么我们拿一个大根堆维护即可。复杂度 \(O(nlog_nlog_v)\)


Code

#include <bits/stdc++.h>
using namespace std;
#define re register
const int _ = 300010;
int n, k, used[_];
long long a[_], l, r, ans, sum;//不开long long见祖宗。
std::priority_queue<int> q;
inline bool check(re long long x){
	while(q. size()){
		q. pop();
	}//原始的,愚蠢的,缓慢的清空方式(但是我因为没清空调了半年)。
	sum = 0;
	for(re int i = 1; i <= n; i ++){
		sum += a[i];
		q. push(a[i]);
		while(sum > x){
			sum -= q. top();
			q. pop();
		}
		used[i] = q. size();
	}//记录可选的最长前缀。
	while(q. size()){
		q. pop();
	}
	sum = 0;
	for(re int i = n; i; i --){
		sum += a[i];
		q. push(a[i]);
		while(sum > x){
			sum -= q. top();
			q. pop();
		}
		if(used[i - 1] + q. size() >= k){//"q. size()"是当前可选的最长后缀,若合法即可返回1。
			return 1;
		}
	}
	return 0;
}
int main(){
	freopen("ace.in", "r", stdin);
	freopen("ace.out", "w", stdout);
	scanf("%d%d", & n, & k);
	for(re int i = 1; i <= n; i ++){
		scanf("%lld", & a[i]);
		r += a[i];
	}
	while(l <= r){//二分答案。
		re long long mid = (l + r) >> 1;
		if(check(mid)){
			r = mid - 1;
			ans = mid;
		}
		else{
			l = mid + 1;
		}
	}
	printf("%lld", ans);
	return 0;
} 

T3 世界(world)

题面大意:(其实我不会)

可能大概或许应该好像看懂了,但是真的不会中译中。


Slove

我不会。 就是一个诡谲大柿子 \(+\) 查找神秘性质来解方程极速版最终做到复杂度由 \(O(m^6)\)\(O(m^3)\)但是我真的不会。【悲】


Code

说了 \(inf\) 遍了,我真的不会。(Dui_Bu_Qi)


T4 染色(paint)

题面大意:

咕咕咕。


Solve

咕咕咕。


Code

这个真的有
#include<bits/stdc++.h>
using namespace std;
#define re register
const int _ = 2e5 + 5;
vector<int> g[_], v[_];
int rain, plc[_], n, q, t, deep[_], z, op, x[_], y[_], son[_], siz[_], top[_], fa[_], dfn[_], num, dis[_], lft[_], rgh[_];
inline void add(re int x, re int y, re int w){
	g[x]. emplace_back(y);
	v[x]. emplace_back(w);
	//cout<<"???";
	return ;
}
inline void dfsa(re int root){
	siz[root] = 1;
	son[root] = - 1;
	lft[root] = ++ num;
	dfn[num] = root;
	for(re int i = 0; i < g[root]. size(); i ++){
//		cout<<"LLL";
		re int sroot = g[root][i];
		if(deep[sroot]){
			continue;
		}
		dis[sroot] = v[root][i];
		fa[sroot] = root;
		deep[sroot] = deep[root] + 1;
		dfsa(sroot);
		siz[root] += siz[sroot];
		son[root] = (son[root] == - 1 || siz[sroot] > siz[son[root]]) ? sroot : son[root];
	}
	rgh[root] = ++ num;
	dfn[num] = root;
	return ;
}
inline void dfsb(re int root, re int tp){
	top[root] = tp;
	if(son[root]){
		dfsb(son[root], tp);
	}
	for(int sroot : g[root]){
		if(sroot != fa[root] && sroot != son[root]){
			dfsb(sroot, sroot);
		}
	}
	return ;
}
inline int lca(re int x, re int y){
	while(top[x] != top[y]){
		if(deep[top[x]] < deep[top[y]]){
			std::swap(x, y);
		}
		x = fa[top[x]];
//		cout<<"MI";
	}
	return deep[x] < deep[y] ? x : y;
}
const int len = _ >> 7;
bitset<100005> bit[_ / len + 10], ans;
inline void init(){
	for(re int i = 1; i <= num; i ++){
		plc[i] = i / len + 1;
		bit[plc[i]][dis[dfn[i]]] = 1 ^ bit[plc[i]][dis[dfn[i]]];
	}
	return ;
}
inline void suan(re int l, re int r){
	for(re int i = l; i <= r; i ++){
		ans[dis[dfn[i]]] = 1 ^ ans[dis[dfn[i]]];
	}
	return ;
}
inline int check(re int l, re int r){
	ans = 0;
	if(plc[l] == plc[r]){
		suan(l, r);
		ans[0] = 0;
		return ans. count();
	}
//	cout<<"BOOM!!!";
	suan(l, plc[l] * len - 1);
	suan((plc[r] - 1) * len, r);
	for(int i = plc[l] + 1; i < plc[r]; i ++){
		ans ^= bit[i];
	}
	ans[0] = 0;
	return ans. count();
}
inline bool query(re int x, re int y){
//	cout<<"JK";
	if(x == y){
		return 1;
	}
	re int l = lca(x, y), num = deep[x] + deep[y] - 2 * deep[l], ql = 0, qr = 0;
	if(l == x || l == y){
		if(deep[x] > deep[y]){
			swap(x,y);
		}
		ql = lft[x] + 1, qr = lft[y];
	}
	else{
		if(lft[x] > lft[y]){
			std::swap(x, y);
		}
		ql = rgh[x], qr = lft[y];
	}
	return check(ql, qr) == num;
}
int main(){
	freopen("paint.in","r",stdin);
	freopen("paint.out","w",stdout);
	scanf("%d%d%d", & n, & q, & t);
	for(re int i = 1; i < n; i ++){
		scanf("%d%d%d", & x[i], & y[i], & z);
		add(x[i], y[i], z);
		add(y[i], x[i], z);
	}
	deep[1] = 1;
	dfsa(1);
	dfsb(1, 1);
	init();
	for(re int i = 1; i < n; i ++){
		if(deep[x[i]] < deep[y[i]]){
			swap(x[i], y[i]);
		}
	}
//	int kkk = 0;
	for(re int i = 1; i <= q; i ++){
		scanf("%d", & op);
		if(op == 1){
			re int a, b;
			scanf("%d%d", & a, & b);
			if(t){
				a ^= rain, b ^= rain;
			}
			bit[plc[lft[x[a]]]][dis[x[a]]] = bit[plc[lft[x[a]]]][dis[x[a]]] ^ 1;
			bit[plc[rgh[x[a]]]][dis[x[a]]] = bit[plc[rgh[x[a]]]][dis[x[a]]] ^ 1;
			dis[x[a]] = b;
			bit[plc[lft[x[a]]]][b] = bit[plc[lft[x[a]]]][b]^1;
			bit[plc[rgh[x[a]]]][b] = bit[plc[rgh[x[a]]]][b]^1;
		}
		else{
			re int x, y;
			scanf("%d%d", & x, & y);
			if(t){
				x ^= rain, y ^= rain;
			}
			re bool as = query(x, y);
			if(as){
				rain = i;
			//	kkk++;
				printf("Yes\n");
				
			}
			else{
				printf("No\n");
			}
		}
	}
	//cout<<q << ' '<<kkk;
	return 0;
}

END.

posted @ 2025-08-01 22:08  养鸡大户肝硬化  阅读(35)  评论(1)    收藏  举报