神秘做题记录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;
}
反思
发现性质很重要,然后要敢想做法,那时候不认为这个题这么简单差点没敢写,但是最后还是写了。
希望以后能切更多紫吧。

浙公网安备 33010602011771号