神秘做题记录2

P3441

思考过程

发现性质,如果两条链不交,那么不优。
推广,三条链,两两相交,肯定不能出环,那就是交于一点,所以所有链交于一点。

题解

我想的用处不大。
正解首先发现性质,链的终点一定是叶子节点。
而且,树的结构使得更深的点数量更多,我们取 \(2l\) 个叶子,一定能取到,然后考虑叶子的父节点,如果有 \(2l\) 个一定能取到 \(2l\) 个,因为假设你先取这一层,任取 \(2l\) 个,那它们一定能连向叶子,同理,如果不满 \(2l\) 个就有多少取多少。
再上一层也是这样,好的你做完了,用拓扑排序给树分层,每一层的点数有 \(cnt\),那么答案为 \(\Sigma min(cnt,2l)\)

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1000010;
int n,m;
vector<int> e[N];
int rd[N],cnt[N];
int dep[N];
bool vis[N];
queue<int> q;
int ans = 0;
void topsort(){
	for(int i = 1;i<=n;i++){
		if(rd[i]==1){
			vis[i] = 1;
			q.push(i);
			dep[i] = 1;
			cnt[1]++;
		}
	}
	while(!q.empty()){
		int x = q.front();
		q.pop();
		for(int i = 0;i<e[x].size();i++){
			int h = e[x][i];
			if(!vis[h]){
				rd[h]--;
				if(rd[h]<=1){
					vis[h] = 1;
					q.push(h);
					dep[h] = dep[x]+1;
					cnt[dep[h]]++;
				}
			}
		} 
	}
}
int main(){
	cin>>n>>m;
	for(int i = 1;i<n;i++){
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
		rd[u]++,rd[v]++;
	}
	topsort();
	for(int i = 1;i<=n;i++){
		ans+=min(2*m,cnt[i]);
	}
	cout<<ans<<"\n";
	return 0;
}

反思

这题真阴飞了,发现了一个我认为很伟大的性质结果屁用没有,神了。
只能说贪心题还是看感觉吧。

P3534

思考过程

最大值最小一眼二分答案。
然后随机猜结论,都错了,感觉很难做。
先想不要求有 \(0\) 的,但是这个我也没想出来,?!蒻蒻!?

题解

二分答案是对的。
先考虑不要求有 \(0\) 的,怎么做?把绝对值拆掉,拆成 \(a_i+z>=a_{i+1}\)\(a_i-z<=a_{i+1}\),然后扫一遍数组,取min,这都能对!?
考虑转移顺序,正着扫,\(i+1\) 已经被 \(i\) 更新了,那么 \(i+1\) 会被更新过的 \(i+1\) 继续更新,这样就一定满足了一个限制。
然后再反着扫,方法同理,最终的序列一定合法,但是最优吗?考虑不能将最小值变大,所以方案唯一,这个就是最优的。
那么再考虑有 \(0\) 的,设当前第 \(i\) 位为 \(0\),这个可以枚举。
限制变成了画两条线,只保留线下部分,上面的减去。
这个贡献怎么O(1)算呢?发现性质,线上部分一定是连续区间!
因为我们更改后的序列一定满足条件,那么一旦有一个位置穿过了这条线,那之后一定上不去了,因为这条线的斜率就是原序列的最大斜率,而要穿过去的话要求斜率更大,这是不可能的。
假设你知道区间 \(l\)\(r\),区间和用前缀和求,然后容斥,把线下面积减去,直线的线下面积使用等差数列求和。
然后就是求左右端点,发现可二分,所以总复杂度 \(nlog^2n\),但是不要这么做,可能卡常,这个 \(log\) 可以去。
继续发现性质,考虑右侧,如果一个点在线上了,那么线往右移它肯定还在,新的点可以加入,对于左侧,\(i\) 的增加可导致点的删除。
所以 \(l\)\(r\) 均单调!使用双指针即可通过。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1000010,inf = 0x3f3f3f3f3f3f3f3f;
int n,m;
int a[N];
int b[N],sum[N];
int ans;
int pos,tmp;
bool check(int z){
	int tot = 0;
	for(int i = 1;i<=n;i++)b[i] = a[i];
	for(int i = 1;i<n;i++)b[i+1] = min(b[i+1],b[i]+z);
	for(int i = n;i>1;i--)b[i-1] = min(b[i-1],b[i]+z);
	for(int i = 1;i<=n;i++){
		sum[i] = b[i]+sum[i-1];
		tot+=a[i]-b[i];
	}
	int l = 0,r = 0;
	int cost = inf;
	for(int i = 1;i<=n;i++){
		while(l<n&&b[l+1]<=abs(l+1-i)*z){
			l++;
		}
		while(r<n&&b[r+1]>abs(r+1-i)*z){
			r++;
		}
		int l_ = l+1;
		int res = sum[r]-sum[l];
		res-=(((i-l_+1)*z)*(i-l_))/2;
		res-=(((r-i+1)*z)*(r-i))/2;
		if(res<cost){
			cost = res;
			tmp = i;
		}
	}
	tot+=cost;
	return (tot<=m);
}
signed main(){
	cin>>n>>m;
	for(int i = 1;i<=n;i++){
		cin>>a[i];
	}
	int l = 0,r = 1e9,mid;
	while(l<=r){
		mid = (l+r)>>1;
		if(check(mid)){
			ans = mid;
			pos = tmp; 
			r = mid-1;
		}else{
			l = mid+1; 
		}
	}
	cout<<pos<<" "<<ans<<"\n";
	return 0;
}

反思

发现性质吧,想到二分答案很显然,但是怎么 check 一直不会,这也没办法了。
总之这个trick我是记住了下次遇到可以用

P3462

思考过程

好多性质。
砝码的种类最多 \(30\),然后发现所有砝码价值相同,也就是先放小的一定不劣,放的是一个前缀。(前提是你得排序)
然后二分这个前缀?check先放大的再放小的,用堆也可以,\(nlog^2n\)
然后自己想着hack了一下,发现无法hack,写一发试试?
然后切了。是的切紫题了,我看到AC之后和范进中举了一样。

题解

为什么先选大的是对的?
因为小的可以拼成大的。
感觉这题想到排序和二分是重要的,很好的贪心题。
千万别降蓝啊

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n,m;
int a[N],b[N];
int ans;
priority_queue<int> q;
bool check(int cnt){
	while(!q.empty())q.pop();
	for(int i = 1;i<=n;i++){
		q.push(a[i]);
	}
	for(int i = cnt;i>=1;i--){
		int x = b[i];
		int h = q.top();
		q.pop();
		if(x>h){
			return false;
		}else{
			h-=x;
			q.push(h);
		}
	}
	return true;
}
int main(){
	cin>>n>>m;
	for(int i = 1;i<=n;i++){
		cin>>a[i];
	}
	for(int i = 1;i<=m;i++){
		cin>>b[i];
	}
	sort(b+1,b+m+1);
	int l = 1,r = m,mid;
	while(l<=r){
		mid = (l+r)>>1;
		if(check(mid)){
			l = mid+1;
			ans = mid;
		}else{
			r = mid-1;
		}
	}
	cout<<ans<<"\n";
	return 0;
}

反思

发现性质很重要,然后要敢想做法,那时候不认为这个题这么简单差点没敢写,但是最后还是写了。
希望以后能切更多紫吧。

posted @ 2026-06-24 11:23  wzc6666  阅读(2)  评论(0)    收藏  举报