题解|2024暑期杭电多校05

【原文链接】

比赛题单:2024“钉耙编程”中国大学生算法设计超级联赛(5)

(1002)HDU7482 Array-Gift

题意

给定一个长度为 n n n 的正整数数组 a a a ,提供以下2种操作:

  1. 选择两个不同的下标 i , j i,j i,j a i ← a i   m o d   a j a_i\leftarrow a_i\ mod\ a_j aiai mod aj
  2. 选择一个下标 i i i 和一个任意正整数 x x x a i ← a i + x a_i\leftarrow a_i+x aiai+x

求使得数组仅有1个非0元素的最小操作次数。

解题思路

add:做之前先对数组去重,因为重复的数可以在开始就通过操作1变为0。

考虑最坏情况的操作次数:
选定两个不同的下标 i , j i,j i,j ,不妨设 a i < = a j a_i<=a_j ai<=aj 。(1次)
做操作2使得 a i = a j + 1 a_i=a_j+1 ai=aj+1 ,再做操作1使得 a i = 1 a_i=1 ai=1 。(1次)
最后对每个 k ≠ i k\neq i k=i 做操作1: a k ← a k   m o d   a i = 0 a_k\leftarrow a_k\ mod\ a_i=0 akak mod ai=0 。( n − 1 n-1 n1次)
最坏情况的操作次数为 n + 1 n+1 n+1

最好的情况:存在一个下标 i i i 使得 a i a_i ai 能够整除数组中所有元素。
满足这个条件的 a i a_i ai 一定是数组中的最小值。
这种情况只需对每个 k ≠ i k\neq i k=i 做操作1: a k ← a k   m o d   a i = 0 a_k\leftarrow a_k\ mod\ a_i=0 akak mod ai=0 ,操作次数为 n − 1 n-1 n1

由于最坏情况难以确定,考虑操作次数为 n n n 的情况:

  1. 数组 a a a 中的最小值经过操作2 , min ⁡ ( a ) ← min ⁡ ( a ) + x \min(a)\leftarrow\min(a)+x min(a)min(a)+x ,能够整除数组中所有元素。
  2. 某个元素经过操作1,能够整除数组中所有元素。

对于第二种情况,维护gcd前缀/后缀(似乎不维护也能过?)。
枚举被修改的数 a i a_i ai 和模数 a j a_j aj ,若 a i   m o d   a j = gcd ⁡ k k ≠ i a k a_i\ mod\ a_j=\gcd\limits_k^{k\neq i}a_k ai mod aj=kgcdk=iak ,则最少操作次数为 n n n

参考代码

void solve()
{
    ll n;cin >> n;
    create_vec(v,n);
    SORT(v);
    vector<ll> tmp;
    for(auto x:v){
        int fl=1;
        for(auto m:tmp) if(x%m==0) {fl=0;break;}
        if(fl) tmp.emplace_back(x);
    }
    ll on=n; v=tmp; n=v.size(); //on是原数组大小
    v.insert(v.begin(),0); v.insert(v.end(),0); //起始下标改为1
    if(n==1) {cout << on-1 << endl;return;}
    
    vector<ll> preg(n+2,0),sufg(n+2,0); 
    FORLL(i,1,n) preg[i]=__gcd(preg[i-1],v[i]);
    FORLL_rev(i,n,1) sufg[i]=__gcd(sufg[i+1],v[i]);

    if(sufg[2]>v[1]&&sufg[2]<=v[2]) {cout << on << endl;return;} //剩下的数模(v1+x)为0
    FORLL(i,1,n){ //被修改的数
        ll g=__gcd(preg[i-1],sufg[i+1]);
        FORLL(j,1,i-1){//模数
            ll t=v[i]%v[j];
            if(t==0) continue;
            if(g%t==0) {cout << on << endl;return;}
        }
    }

    cout << on+1 << endl;
}

(1006)HDU7486 猫罐头游戏

题意

有三堆猫罐头,每堆猫罐头的数量分别为 a , b , c a,b,c a,b,c
两只猫猫进行博弈,每次可以选择一堆猫罐头全部吃掉,然后再从剩下的两堆中选择一堆猫罐头分成两份(每份至少一罐)。
不能操作的猫猫输掉游戏。

问先手是否有必胜策略。

解题思路

最终必败态是 a = b = c = 1 a=b=c=1 a=b=c=1

当开局有3个奇数时,不论怎么操作,结果都是2奇数1偶数;
当开局有1个或2个偶数时,可以控制结果为3个奇数。
三个奇数的尽头就是3个1必败,因此开局3奇数必败,开局1或2偶数必胜。

开局全偶数的情况,可以从lowbit的角度考虑。
当开局三个数lowbit位于同一位时,不论怎么操作都不能使lowbit相等。
当开局三个数lowbit位于不同位时,可以通过操作控制lowbit相等,整个过程lowbit减小。
lowbit相等的尽头是三个数的lowbit都等于 1 1 1也就是三奇数的必败态。

因此,开局三个数lowbit相等必败。
观察到三个数不全是偶数的情况也可以归并到这个结论中,2行秒了。

参考代码

void solve()
{
    ll a,b,c; cin >> a >> b >> c;
    if(lowbit(a)==lowbit(b)&&lowbit(b)==lowbit(c))  NO; else YES;
}

(1011)HDU7491 开关灯

题意

n n n 盏灯排成一排,编号为 1 ∼ n 1\sim n 1n ,初始状态为全灭。
每次操作可以选择一盏灯,反转这盏灯和与之相邻的灯的状态(开变关,关变开)。
求任意操作后,这排灯可以到达多少种不同的状态。

解题思路

观察样例发现答案满足 2 n 2^n 2n
手玩发现只有 n ≡ 2 ( m o d   3 ) n\equiv2(mod\ 3) n2(mod 3) 时达不到全亮状态,是 2 n − 1 2^{n-1} 2n1

如果发现力不够,还可以考虑以下方法:
对位置1,2分别做一次操作,可以单独反转3;再对4,5分别做一次操作,可以单独反转6。
以此类推,正着做可以单独反转所有 i ≡ 0 ( m o d   3 ) i\equiv0(mod\ 3) i0(mod 3),反着做可以单独反转所有 i ≡ n − 2 ( m o d   3 ) i\equiv n-2(mod\ 3) in2(mod 3)
如果可以单独任意反转连续2个灯,则与它们相邻的灯也可以单独任意反转,以达到所有 2 n 2^n 2n 种状态。

当且仅当 n ≡ 2 ( m o d   3 ) n\equiv2(mod\ 3) n2(mod 3) 时,正着做和反着做反转的都是同一批灯,无法通过以上操作任意反转所有的灯。
由于无法到达全亮状态,答案少一半,为 2 n − 1 2^{n-1} 2n1

参考代码

void solve()
{
    ll n;cin >> n;
    if(n%3==2) cout << qcpow(2,n-1) << endl;
    else cout << qcpow(2,n) << endl;
}

(1013)HDU7493 飞行棋

题意

0 ∼ n 0\sim n 0n 号格子排成一排,棋子初始在 0 0 0 号格,目标是精确到达第 n n n 格。
每次随机从 1 ∼ n 1\sim n 1n 中均匀随机一个正整数 x x x ,然后向前移动 x x x 步,若移动到第 n n n 格还有剩余步数,则会往回走。
如果随机到 n n n 且最终没有精确到达第 n n n 格,则可以再从 1 ∼ n − 1 1\sim n-1 1n1 中均匀随机一个正整数并行动。

求精确到达第 n n n 格的期望步数。

解题思路

0 0 0 号格子一次到达第 n n n 格的概率是 1 n \dfrac{1}{n} n1
1 ∼ n − 1 1\sim n-1 1n1 号格子一次到达第 n n n 格的概率都是 1 n ( 1 + 1 n − 1 ) = 1 n − 1 \dfrac{1}{n}(1+\dfrac{1}{n-1})=\dfrac{1}{n-1} n1(1+n11)=n11 ,期望为 n − 1 n-1 n1
如果没有到达终点,棋子只会落在 1 ∼ n − 1 1\sim n-1 1n1 号格子上。

因此答案为 1 n + n − 1 n × ( ( n − 1 ) + 1 ) = n + 1 + 1 n \dfrac{1}{n}+\dfrac{n-1}{n}\times ((n-1)+1)=n+1+\dfrac{1}{n} n1+nn1×((n1)+1)=n+1+n1
(式子中 ( n − 1 ) + 1 (n-1)+1 (n1)+1 是加上了从0开始的第一步)

参考代码

void solve()
{
    ll n;cin >> n;
    cout << sub(add(inv(n),n),1) << endl;
}
  • 26
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深翼CCLMSY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值