2025杭电多校第四场 回忆与展望、量子弹弓 个人题解

ps:由于本人一直补不出咖啡题,所以只能写两道签到题的题解了

量子弹弓

#贪心 #图

题目

![[Pasted image 20250731092337.png]]

思路

本题有一个理解的关键点: [ 1 , n ] [1,n] [1,n]的排列 a a a
如果没有发现给出的点可以自己随意排列,那么本题将会卡死

其次,如何理解量子弹弓强度

实际上题目的意思是:

  • 构造一棵树,使得可以从起点走到终点,再从终点走到起点
  • 需要保证每个点都走过
  • 每个点都只能作为走一步的起点与终点各一次
  • u u u走到 v v v时,需要保证 d i s ( u , v ) ≤ W u dis(u,v)\leq W_{u} dis(u,v)Wu,其中 W u W_{u} Wu u u u的量子弹弓强度

当所有强度都为1时,很好构造,此时的图即为一条链:
![[Pasted image 20250731093604.png]]

当出现了长度大于等于2的点时(后文解释为什么为2),则出现了一个个“跳板”:
![[Pasted image 20250731094309.png]]

我们采取第一次走完所有的1,接下来走完所有强度大于1的点的贪心策略
图中黄线为第一次走完所有1的路线
![[Pasted image 20250731094827.png]]

为了区分每一次走了多少步,我给每个点走的路都换了颜色~
由于在从终点返回的路上不可以停在来时路上,所以我们需要再来时路(1构成的链)上用强度大于 2的点建跳板
每一次都从当前跳板走到下一个跳板上,直到走到终点

不难发现,每一个强度为 k k k的跳板对于在单链上返程的贡献都为 k − 2 k-2 k2步,因为走出当前跳板和进入下一个跳板各要消耗一步
特别地,在下一个目的地是终点或者当前点是起点的时候,消耗的步数从2变成了1,对返程的贡献为 k − 1 k-1 k1

此外,强度为2的点没有任何贡献,但她们也可以被上述理论所概括

因此我们只需要统计所有强度为1的点的个数,剩余的点都去累积其对返程的贡献

总贡献需要减2来计算返程起点与终点的特殊贡献

如果返程总贡献小于了1链的长度,那么返程失败 ,否则成功

别忘了各种奇妙特判哦~

代码实现

#include<iostream>
#include<vector>
#include<cmath>
#include<algorithm>
#include<set>
using namespace std;
using ll = long long;
#define rep(i, a, b) for(ll i = (a); i <= (b); i++)
#define per(i, a, b) for(ll i = (a); i >= (b); i--)
#define mid ((l+r)>>1)
#define see(stl) for(auto&ele:stl)cout<<ele<<" "; cout<<'\n';

const ll N = 2e5+5,mod=998244353,inf=0x3f3f3f3f3f3f3f3f;
ll p[N];
void solve() {
    ll n;cin>>n;ll ma=0,cnt=0,sum=0;
    bool zero=0;
    rep(i,1,n){
        cin>>p[i];
        ma=max(ma,p[i]);
        sum+=max(0ll,p[i]-2);
        if(p[i]==0)zero=1;
        if(p[i]==1)cnt++;
    }
    sum+=2;

    if(n==1){
        cout<<"YES\n";return;
    }
    if(zero){
        cout<<"NO\n";return;
    }
    if(n==2&&p[1]>=1&&p[2]>=1){
        cout<<"YES\n";return;
    }
    if(sum<cnt){
        cout<<"NO\n";return;
    }
    cout<<"YES\n";
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--)solve();
    return 0;
}

回忆与展望

#数学 #模拟 #贪心

题目

![[Pasted image 20250731100050.png]]

思路

实际上我们很容易想到,这道题只有两个策略,尽可能多的单调增(增减增减),尽可能多的单调不减
![[Pasted image 20250731101747.png]]

然而实际上这道题不能选中其中一个策略贯彻到底,需要找到一个临界点来区分这两种策略哪个更优
![[Pasted image 20250731101940.png]]

开始时先采取增减增减策略,为了保障尽可能多的 x x x,我们每次选取的上升段的长度都要尽可能大,这就导致了每次选取的上升段长度会越来越小

而上述两种策略就可以转化为对于每次选出来的上升段(长度为 l e n len len)进行不同的两种操作:

  • 增减增减:
    • 加入 l e n − 1 len-1 len1 x x x贡献,加入一个 z z z贡献
  • 单调不减:
    • 将这 l e n len len个数并入之前的上升段中,即加入 l e n len len y y y
      因此比较 x × ( l e n − 1 ) + z x\times(len-1)+z x×(len1)+z y × l e n y\times len y×len即可知道何时选用何种策略

至于如何找出所有的上升段的长度,我们可以开一个桶数组 c n t [ N ] cnt[N] cnt[N]记录每个数字出现的次数,桶数组 t e r m [ N ] term[N] term[N]记录第 i i i个上升段的长度
每遍历一个 a i a_{i} ai c n t [ a i ]  ⁣ +  ⁣ + , t e r m [ c n t [ a i ]  ⁣ +  ⁣ + ] cnt[a_{i}]\!+\!+,term[cnt[a_{i}]\!+\!+] cnt[ai]++,term[cnt[ai]++]

代码实现

#include<iostream>
#include<vector>
#include<cmath>
#include<algorithm>
#include<map>
#include<unordered_map>
using namespace std;
using ll = long long;
#define rep(i, a, b) for(ll i = (a); i <= (b); i++)
#define per(i, a, b) for(ll i = (a); i >= (b); i--)
#define mid ((l+r)>>1)
#define see(stl) for(auto&ele:stl)cout<<ele<<" "; cout<<'\n';

const ll N = 5e6+5,mod=998244353,inf=0x3f3f3f3f3f3f3f3f;
ll a[N];
unordered_map<int,int>cnt;
map<int,int>term;
void solve() {
    cnt.clear();term.clear();
    int n,x,y,z;cin>>n>>x>>y>>z;
    rep(i,1,n)cin>>a[i];
    rep(i,1,n){
        cnt[a[i]];
        cnt[a[i]]++;
        term[cnt[a[i]]];
        term[cnt[a[i]]]++;
    }
    ll ans;
    bool find=0;
    int pos=0;
    for(auto&ele:term){
        pos++;
        ll len=ele.second;
        if(pos==1){
            ans=len*x;continue;
        }
        ll cmp=(x-y==0)?inf:(int)ceil((1.0)*(x-z)/(x-y));
        if(len<cmp){
            find=1;
        }
        if(!find){
            ans+=(len-1)*x+z;
        }else{
            ans+=(len)*y;
        }
    }
    cout<<ans<<'\n';
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--)solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值