edu cf #138 Div.2(A~D)

edu cf #138 Div.2

A. Cowardly Rooks

  • 题意

    • 给予一个n*n的棋盘,里面放有m个棋子,棋子保证每一行每一列最多只有一个棋子
    • 现在询问,能否变换其中一个棋子,使得棋盘中所有棋子仍然符合每行每列最多只有一个棋子
  • 题解

    • 贪心,和n皇后思路一致。棋盘上最多可以放置n个棋子满足要求,m>n时必然无法实现;m=n时只有给出的这一种唯一确定的符合要求的状态,无法变换一个棋子的位置得到符合要求的状态;m<n时都可以
  • 代码

#include <iostream>

using namespace std;

bool solve() {
    int n,m; cin>>n>>m;
    
    for(int i=0;i<m;i++) {
        int x,y;
        cin>>x>>y;
    }
    
    return m<n;
}

int main() {
    int t; cin>>t;
    while(t--) cout<<(solve() ? "YES":"NO")<<'\n';
    
    return 0;
}

B. Death’s Blessing

  • 题意

    • 有n个怪物,第i个怪物有a[i]血量,同时有b[i]的回血包,回血包在i死亡后会使得i的左右邻居都获得b[i]血量加成
    • 每一秒可以打出伤害为1的攻击,求消灭所有怪物的最小时间
  • 题解

    • 贪心,模拟样例可知,除去最后一个被击杀的怪物,其他怪物的死亡都会带来怪物总血量的增加,所以把血包最大的最后击杀即可使其血包失效;除去最后一个怪物血包用不上,其他的怪物死亡一定都会使得怪物总血量增加,而每次如果只击杀左右两端的怪物,其血包只会使用一次,不使用两次,贪心来看,是最优的
    • 怪物的血量相当于a[i]+b[i],血包最大怪物的血量为a[i]
  • 代码

#include <iostream>
#include <algorithm>

using namespace std;
const int N=110;

int n,a[N];

bool check(int k) {
    int pos=n;
	while(a[pos]>k)--pos;
	
	for(int i=1;i<=k;++i){
		while(a[pos]>k-i+1)--pos;
		if(pos<i)return 0;--pos;
	}
	
	return 1;
}

void solve() {
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    
    sort(a+1,a+n+1);
    for(int i=n;i>=0;i--)
        if(check(i)) {
            cout<<i<<'\n';
            return ;
        }
}

int main() {
    int t; cin>>t;
    while(t--) solve();
    
    return 0;
}

C. Number Game

  • 题意

    • 给定长度为n的数组,Alice选择一个数字k,而后进行k轮游戏
    • 第i轮游戏:Alice选择一个小于等于k-i+1的数删除,Bob选择任意一个数加上k-i+1
    • 如果在k轮内,Alice无法删除数字,那么Alice输掉,否则赢
    • 问最大的k
  • 题解

    • 贪心,模拟,双指针。将数组排序后,发现Alice贪心的选能够选择范围中最大的值删除,以保证有小的值可以维持下一轮的游戏;Bob一定贪心选择Alice的范围内最小值去添加,使得游戏尽早结束,越早意味着越有可能Alice撑不到k轮就结束,所以这么选;并且bob操作完的数,计算后可知,再也不会出现在Alice可选择的范围内,相当于失效数。
    • 排序数组后,因为数据小,暴力枚举k,因为要找最大的k,所以从n到0枚举,第一个符合要求的k即为最大值。验证k是否符合要求时,用双指针直接模拟即可,i指针代表bob,pos指针代表Alice
  • 代码

#include <iostream>
#include <algorithm>

using namespace std;
const int N=110;

int n,a[N];

bool check(int k) {
    int pos=n;//pos代表Alice操作的位置
	while(a[pos]>k)--pos;//第一个小于等于k的数的位置
	
	for(int i=1;i<=k;++i){//i从头开始
		while(a[pos]>k-i+1)--pos;//第一个符合要求的数的位置
		if(pos<i)return 0;--pos;//如果指针已经相互穿过,即该轮没有可以选的数,那么不符合要求
	}
	
	return 1;
}

void solve() {
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    
    sort(a+1,a+n+1);
    for(int i=n;i>=0;i--)//暴力枚举k
        if(check(i)) {
            cout<<i<<'\n';
            return ;
        }
}

int main() {
    int t; cin>>t;
    while(t--) solve();
    
    return 0;
}

D. Counting Arrays

  • 题意

    • 给一个下标从1到n的数组,当且仅当gcd( a[i] , i )=1时可以删除a[i],后面元素的下标依次顺移
    • 将a数组全部删除所选的位置组成的数组为删除数组b
    • 定义数组a为ambiguous arrary,当仅当其对应的删除数组种类>1
    • 若a数组长度为1~n中任意一个,但所有元素都属于[1,m],问有多少个ambigous arrary
  • 题解

    • 思维+组合+逆向思维。假定数组a的长度为n来分析题目
    • 对于任意数组a,一定有一个符合要求的删除数组b=[1,1,1…]。如果正面思考,那么就去验证每一种数组的情况看是否还存在另一种删除数组,显然有点难;那么考虑对立,有哪些数组只有上述的一种删除数组,然后数组总数减去不符合条件的数量即为答案。似乎更有眉目了,因为除了第一个位置其他位置都要符合 gcd( a[i] , j ) != 1 (1<j<=i)
    • 解释一下上述分析,因为只能够从第一个位置删除,那么对于第i个数,它删除的时候,要满足以下所有条件才能在保证只在第一个位置删除,条件为gcd( a[i] , i ) != 1,gcd( a[i], i-1 ) != 1…,gcd( a[i] , 2 ) != 1,gcd( a[i] , 1) == 1
    • 如何计算数组总数,对于长度为n的数组,每一个元素属于[1,m],所以数组的数量总数为m^n
    • 如何计算只有一种删除数组的数组数量,即如何计算每一个位置都满足第三点描述条件的数组数量。gcd!=1意味着不是互质关系而是倍数关系,所以a[i]要是所有2~i质数的倍数(为什么是质数,因为所有合数可以用质数凑出来,所以只要是范围内的所有质数倍数即可),算一个最小元倍数a=pi(质数2~n),那么对于位置i有m/a个符合要求的数,那么根据乘法计数原理,n个位置符合要求的数相乘即为符合要求的只有一个删除数组的数组数量。
    • 最终答案为,所有数组的总和sum-不符合要求的数组数量sub
  • 代码

#include <iostream>

using namespace std;
typedef long long LL;
const LL INF=1e12;
const int mod=998244353,N=100;

int n,cnt;
LL m,s[N];//数组s[i]存放前i个位置的[2,n]的质数乘积

bool is_prime(int x) {//判断质数
    for(int i=2;i<x;i++) if(x%i==0) return 0;
    return 1;
}

int main() {
    cin>>n>>m;
    s[0]=1;//初始化,便于循环语句的书写
    while(s[cnt]<=INF) {//如果某个位置i的质数乘积已经大于m的最大范围,那么没有可能存在这样的a[i]可以满足分析中的条件。
        ++cnt; s[cnt]=s[cnt-1];
      //把所有不超过最大范围的位置的质数乘积一一计算出来
        if(is_prime(cnt)) s[cnt]*=cnt;
    }
    
    LL ans=0,Pow=1,sub=1;//分别记录答案,数组的总数,不符合条件的数组数量
    for(int i=1;i<=n;i++) {//题目要求的是1~n的长度的答案和,所以相加
        Pow=(Pow*(m%mod))%mod;//此长度的数组总数
        ans+=Pow;
        if(i<=cnt) sub=sub*((m/s[i])%mod)%mod;
        else sub=0;//此判断是为了防止除0情况,因为i已经超过了最大位置范围cnt,那么肯定不存在这样的删除数组
        ans+=mod-sub;//总数减去不符合条件的,记得取模
    }
    cout<<ans%mod<<'\n';
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值