NewCoder Weekly Contest 46

NewCoder Weekly Contest 46

C 爱音开灯 简单数论

Problem Statement

有无穷个灯排成一排,编号为从 1 开始,初始时所有灯都是关闭的。改变第 \(i\) 个灯的开闭状态会同时改变所有编号为 \(i\) 倍数的灯的开闭状态。

Anon会从 1 到 \(n\) ,依次改变每一个灯的开闭状态,她想知道第 \(x\)​ 个灯最终的状态是什么?如果灯是关闭的,输出 "OFF" ,否则输出 "ON" 。

Input

输入两个正整数 \(n,x(1 \leq n,x \leq 10^{12})\)​ 。

Output

输出一个字符串表示答案。如果灯是关闭的,输出 "OFF" ,否则输出 "ON" 。

Sample

1 1

输出

ON

说明

Anon开第 1 个灯时,第 1 个灯从关闭到开启;

答案为 "ON" 。

Solution

观察题目,先想暴力做法,既然第 \(i\) 个灯的开闭状态会同时改变所有编号为 \(i\) 倍数的灯的开闭状态,那我们就枚举1~n盏灯,如果i是x的因子,就改变x的开闭状态,可以通过50%的数据,IOI赛制下也能拿分

Code
#include<bits/stdc++.h>
#define int long long

using namespace std;

int n, k;

void solve()
{
    cin >> n >> k;
    bool flag = false;
    for (int i = 1; i <= n; i ++ )
    {
        if (k % i == 0) flag -= 2 * flag;
    }
    if (flag = true) cout << "ON" << endl;
    else cout << "OFF" << endl;
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    
    solve();

    return 0;
}

优化代码

思路: 带限制的因子数求解

观察题目,第 \(i\) 个灯的开闭状态会同时改变所有编号为 \(i\) 倍数的灯的开闭状态,所以第x个灯的开闭状态只会被i是x的因数的灯所影响,而由数论部分知识(试除法分解质因数)我们知道,x的因数一定是成对出现的,若ix的因子,那么x / i 也是x的因子,所以有\(其中一个x的因子 <= \sqrt{x}\),所以我们只需要枚举这个较小的x的因子,利用它来求较大的因子是否满足情况,这样可以大大降低循环次数,从而降低时间复杂度,只需要枚举\(\sqrt{x}\)次,使得时间复杂度为\(O(\sqrt{x})\),x上限是\(10^{12}\),最多循环\(\sqrt{10^{12}}=10^{6}\)

Code
#include<bits/stdc++.h>
#define int long long

using namespace std;

int n, x;
int sum; //记录第x盏灯最终需要改变多少次
void solve()
{
    cin >> n >> x;

    for (int i = 1; i <= x / i; i ++ ) //只用对x的其中一个因子枚举,这个因子一定<=sqrt(x),这样可以将时间复杂度降到O(sqrt(n))级,就不会超时
    {
        if (x % i == 0) //如果i是x的因子
        {
            if (i <= n) //同时第i盏灯在操作的范围内
            {
                sum ++ ;
                if ((x / i <= n) && (x / i != i)) //判断另一个因子x / i 是否会被点亮 以及是否和i相等
                {
                    sum ++ ;
                }
            }
        }
    }

    if (sum % 2 == 0) cout << "OFF" << endl; //如果操作次数是偶数 最终灯还是没开
    else cout << "ON" << endl;
    return;

}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    
    solve();

    return 0;
}

D 小灯做题 BFS

Problem Statement

Tomori在做一个题目,这个题目初始有四个非负整数 \(a,b,c,k\)

每次Tomori可以对 \(a, b, c\) 进行操作:选择其中两个数 \(x,y\),然后将剩下一个数改成 \(\operatorname{mex}(x,y)\)

Tomori想知道她最少操作多少次,能让 \(a,b,c\) 中的一个数变成 \(k\)。若无解,则输出 \(-1\)

\(\operatorname{mex}(x,y)\) 表示最小的非负整数 \(p\) ,满足 \(p\ne x\)\(p\ne y\) ,例如 \(\operatorname{mex}(1,2)=0,\operatorname{mex}(0,2)=1\)​ 。

Input

第一行输入一个正整数 \(T(1 \leq T \leq 100)\) ,表示询问次数。

接下来 \(T\) 行,每行输入四个非负整数 \(a,b,c,k(0 \leq a,b,c,k \leq 10^9)\) ,表示询问。

Output

对于每个询问,在一行中输出一个整数表示答案。

Sample

输入

3
1 2 3 1
1 2 3 0
1 2 3 4

输出

0
1
-1

说明

第 1 个询问:  
第 1 个数已经是1了;  
不需要进行操作。  
第 2 个询问:  
选择第 1、2 个数,将第 3 个数变成 mex(1,2) = 0 ;  
只需要进行 1 次操作。

Solution

题目告诉我们要在a b c中选两个数进行\(mex(x, y)\)​操作,使得其等于k

\(\operatorname{mex}(x,y)\) 表示最小的非负整数 \(p\),其实可以很容易发现,两个数求出来的\(\operatorname{mex}(x,y)\)只会在0 1 2这三个数中出现

所以𝑘值(𝑘>=3)无法通过mex操作得到,只有刚开始k就等于a或b或c时,才能满足条件,操作次数为0,特殊处理即可

所以我们只需要罗列mex(x, y)分别等于0 1 2的情况

做的时候一直没有AC的原因之一就是mex的情况罗列错了,实际上不用那么麻烦 反正mex算出来的值就0 1 2三种情况,我们从小到大枚举看哪个满足条件就行了

int mex(int a,int b){
	for(int i=0;;i++){
		if(i!=a&&i!=b)return i;
	}
}

接着,我们可以利用BFS,同时对bfs(a,b,mex(a,b),k,dep+1); bfs(a,mex(a,c),c,k,dep+1); bfs(mex(b,c),b,c,k,dep+1);进行搜索,return的条件是if (k == a || k == b || k == c) return cnt;,搜的时候我们要顺便记录其操作次数dep,同时加上一个避免无限向下递归的条件(做的时候没加导致MLE),if (cnt > 5) return cnt; ,由于k只能从0 1 2中选取,a b c经过几次mex()操作就肯定能得到,如果没有得到k,那么永远也不会得到。操作次数最多的情况就是 a = 3 b = 3 c = 3 k = 2 需操作3次才能得到k

我们就给向下搜索限定一个层数,也就是最高操作次数,限定为5,任取即可,只要使得不会MLE

//自己写的版本
#include<bits/stdc++.h>
#define int long long

using namespace std;

int T;
int a, b, c, k;
int cnt;

//浪费时间的条件判断 容易出错还浪费时间
int mex(int a, int b)
{
    if (a != 0 && b != 0)
        return 0;
    if ((a == 0 && b == 1) || (a == 1 && b == 0))
        return 2;    
    if (a == 0 || b == 0)
        return 1;
    return -1;
}

int dfs(int a, int b, int c, int cnt)
{
    if (cnt > 5) return cnt; //关键点 使得不会无限向下搜索
    
    //麻烦的设置数据,目的是为了全部三种情况返回后,return其中的最小值,看看范式怎么写
    int cnt1 = 9999, cnt2 = 9999, cnt3 = 9999;
    if (k == a || k == b || k == c) return cnt;
    
    cnt1 = dfs(a, b, mex(a, b), ++ a1);
    cnt2 = dfs(a, mex(a, c), c, ++ b1);
    cnt3 = dfs(mex(b, c), b, c, ++ c1);
    return min(cnt1, min(cnt2, cnt3));
}

void solve()
{
    cin >> a >> b >> c >> k;
    if (k == a || k == b || k == c)  //其实在dfs函数里预先判断这种情况就行了
    {
        cout << 0 << endl;
        return;
    }
    if (k != 0 && k != 1 && k != 2)
    {
        cout << -1 << endl;
        return;
    }
    cnt = dfs(a, b, c, 0);
    if (cnt == 5) cout << -1 << endl;
    cout << cnt << endl;
    return;
    
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin >> T;
    while (T -- )
        solve();

    return 0;
}

范式 以后就按着这个模板思考

Code
#include<bits/stdc++.h>
#define int long long

using namespace std;

int T;
int a, b, c, k;
int res;

//不用去想那么多 所有数求出来的mex()最大就是2 肯定不会有很多重循环 直接循环就是了 不用在那特判 
int mex(int a, int b){
	for(int i = 0; ; i++){
		if(i != a && i != b) return i;
	}
}

//不设返回值 答案存在res里
void bfs(int a,int b,int c,int dep){
	if (dep > 5) return;
	if(a == k||b == k||c == k){ //把初始时k就等于a b c其中一个情况也融进去了 dep此时等于0
		res = min(res, dep); return; //直接开一个res记录答案,避免复杂地设置变量
	}
	bfs(a,b,mex(a,b),dep+1);
	bfs(a,mex(a,c),c,dep+1);
	bfs(mex(b,c),b,c,dep+1);
}

signed main(){
	ios::sync_with_stdio(0); cin.tie(0);
	cin>>T;
	while(T--){
		int a,b,c;
		cin>>a>>b>>c>>k; 
        res=666; //初始时设成极大值,避免影响答案
		bfs(a,b,c,0);
		if(res==666)res=-1; //如果res没变,说明不可能操作后得到k
		cout<<res<<endl;
	}
}

E 立希喂猫 二分

Problem Statement

Taki买了 \(n\) 种猫粮,第 \(i\) 种猫粮的营养值为 \(a_i\) 、数量为 \(b_i\)

猫猫的饭量是无穷的,每一天她可以吃任意数量的猫粮,但是同一种猫粮她一天只会吃一次。

Taki想知道在 \(k\) 天内,猫猫可以获得的最大营养值之和是多少。

Input

第一行输入一个正整数 \(n(1 \leq n \leq 10^5)\) ,表示猫粮种数。

第二行输入 \(n\) 个正整数 \(a_i(1 \leq a_i \leq 10^4)\) ,表示每种猫粮的营养值。

第三行输入 \(n\) 个正整数 \(b_i(1 \leq b_i \leq 10^9)\) ,表示每种猫粮的数量。

第四行输入一个正整数 \(q(1 \leq q \leq 10^5)\) ,表示询问次数。

接下来 \(q\) 行,每行输入一个正整数 \(k(1 \leq k \leq 10^9)\) ,表示询问的天数。

Output

对于每个询问,在一行中输出一个整数表示答案。

Sample

输入

3
3 7 5
1 2 1
3
1
2
3

输出

15
22
22

说明

一种可行的方案是:  
第 1 天:  
猫猫吃第1、2、3种猫粮,营养值之和为3+7+5=15。  
第 2 天:  
猫猫吃第2种猫粮,营养值之和为7。  
第 3 天:  
猫猫不吃猫粮。

Solution

观察题目,关键点在同一种猫粮她一天只会吃一次,也就是说k天他最多吃这种猫粮k次,要是猫粮数量不够它最多吃 \(b_{i}\)次,这就是破局的关键,那么如果我们按照猫粮数量从小到大排序,必然前面的猫粮数量小于k,后面的猫粮数量大于k,这样就有二分的性质了,排好序的猫粮序列,前i种猫粮满足性质\(k > b_{i}\),后n-i种猫粮不满足此性质,这样就可以二分区间,找到满足性质的右边界,这种猫粮时最后一个满足\(k > b_{i}\)的猫粮,它在k天内能被吃完,营养值是\(a_{i}*b_{i}\),在这种猫粮后面的猫粮由于\(k < b_{i}\),k天内最多吃k次,营养值是\(k * a_{i}\)

在这个过程中我们利用前缀和后缀和优化,预先计算出前i个猫粮全能吃\(b_{i}\)次的营养值之和 和 后n-i个猫粮都吃不满\(b_{i}\)次 只能吃k次 的营养值之和 由于k后面才会给 我们就先算\(a_{i}\)之和 后面乘上k就行

这样预处理之后,二分算出右边界之后,我们就可以对算出的最后一个满足性质\(k > b_{i}\)的猫粮之前的猫粮利用前缀和 这之后的猫粮利用后缀和,算出总营养值,具体看代码

image-20240610232111637

Code
#include<bits/stdc++.h>
#define int long long

using namespace std;

const int N = 1e5 + 10;

int n, q, k;
int s1[N], s2[N];

struct Maoliang
{
    int a, b;
    bool operator < (Maoliang &W)const  //重载运算符 按照每种猫粮的数量进行排序
    {
        return b < W.b;
    }
}ml[N];


signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    
    cin >> n;
    for (int i = 0; i < n; i ++ )
    {
        int a;
        cin >> a;
        ml[i].a = a;
    }
    for (int i = 0; i < n; i ++ )
    {
        int b;
        cin >> b;
        ml[i].b = b;
    }

    //将猫粮按照数量排序
    sort(ml, ml + n);

    //求前i个猫粮的营养值*数量的前缀和
    s1[0] = ml[0].a * ml[0].b;
    for (int i = 1; i < n; i ++ ) s1[i] += s1[i - 1] + ml[i].a * ml[i].b;

    //求第i到第n - 1个猫粮的营养值后缀和
    s2[n - 1] = ml[n - 1].a;
    for (int i = n - 2; i >= 0; i -- ) s2[i] += s2[i + 1] + ml[i].a;
    
    
    cin >> q;
    while (q -- )
    {
        cin >> k;
        
        int l = 0, r = n - 1; //在第0~n-1种猫粮的区间内二分,找ml[i].b>=k的右边界
        while (l < r)
        {
            int mid = (l + r + 1) / 2;
            if (k >= ml[mid].b) l = mid;
            else r = mid - 1;
        }

        if (r == 0 && k < ml[r].b)   cout << k * s2[0] << endl; //特殊情况判断 当所有猫粮数量都比k大时,那我们所有的猫粮都只能吃k个 (被卡了好久)
        else cout << s1[r] + s2[r + 1] * k << endl; //前r个猫粮可以吃ml[r].b个,后n - r个猫粮只能吃k个
    }
    //cout << ml[n - 1].a << " " << ml[n - 1].b << endl;
    return 0;
}

刚开始模仿题目最大生产写出的题解 时间复杂度是n * q * logn 也就是\(O(n^2log_{2}{n})\)​,会超时

而且l的初始值设成0也调了好久

二分需要注意的两个点

  • 待二分的区间必须是有序的
  • 求左边界是l + r >> 2 求右边界是l + r + 1 >> 2 一定不要忘了
暴力Code
#include<bits/stdc++.h>
#define int long long

using namespace std;

const int N = 1e5 + 10;

int n, q, k;
int a[N], b[N];

bool check(int mid)
{
    //cout << k << endl;
    int res = mid;
    for (int i = 0; i < n; i ++ )
    {
        if (k <= b[i])
        {
            res -= k * a[i];
        }
        else
        {
            res -= b[i] * a[i];
        }
        if (res <= 0) return true;
    }
    return false;
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    
    cin >> n;
    for (int i = 0; i < n; i ++ ) cin >> a[i];
    for (int i = 0; i < n; i ++ ) cin >> b[i];
    cin >> q;
    while (q -- )
    {
        cin >> k;
        int l = 0, r = 1e18; //注意!求右边界是int mid = (l + r + 1) / 2; 否则会出现陷入死循环问题 忘了导致debug半天
        while (l < r)
        {
            cout << r << endl;
            int mid = (l + r + 1) / 2;
            if (check(mid)) l = mid;
            else r = mid - 1;
        }

        cout << r << endl;
    }

    return 0;
}

F 祥子拆团 数论

Problem Statement

Sakiko有两个数字 \(x,y\) ,她想知道,有多少种方式可以将 \(x\) 拆成 \(y\) 个正整数的乘积。

例如 \(x=6,y=2\) 时,有 \(6 \times 1=6,3 \times 2=6,2 \times 3=6,1 \times 6=6\) 这 4 种方法。

由于这个答案可能很大,因此你需要输出答案对 \(10^9 + 7\) 取模后的结果。

Input

第一行输入一个正整数 \(T(1 \leq T \leq 10^3)\) ,表示询问次数。

接下来 \(T\) 行,每行输入两个正整数 \(x,y(1 \leq x,y \leq 10^9)\) ,表示询问。

Output

对于每个询问,在一行中输出一个整数表示答案。由于这个答案可能很大,因此你需要输出答案对 \(10^9 + 7\) 取模后的结果。

Sample

输入

2
6 2
12 2

输出

4
6

说明

第 2 个询问:  
[12,1],[6,2],[4,3],[3,4],[2,6],[1,12]  
答案为6

Solution

这个题我是这么理解的,我们先用i找x的质因子(i < x / i , 只用找一对因子中的一个),找到一个因子i,用while (x / i == 0) x /= i; d++;把x除尽,这时候cnt就是x里质因子i的个数(幂),这时候由于x内不同的质因子彼此独立(前提条件),while循环结束后我们将质因子i全部从x里分离了出来,个数为d,下一次for循环再去筛下一个质因子,相当于到最后我们把x变成了由其多个质因子的d次方构成的数

找到一个因子i后,我们将其不断从x内分离,最后一次x /= i完成后得到不能被i取余的质因子x,相当于把x拆成了\(i^d*x\),一共d+1个因子,要满足拆成y个正整数乘积,其余y-d-1个空位补上1,我们就能满足题目要求的 拆成 \(y\) 个正整数的乘积。

隔板法,将d个球放进y个盒子内,盒子可以有空位,可以看成有n个相同的球,m个不同的盒子,把n个球放到盒子里,盒子允许为空,有多少种方案,把d个因子看成n个相同的球,把y个数看成m个不同的盒子,把d个球放到盒子里,盒子允许为空,有多少种方案。 d个球有d-1个空隙,这里面可以利用隔板插进空隙将球分成y组 有C(y-1,d)种方案,由于盒子可以为空,则为 C(y + d - 1, d),这是对于x的一个因子i的情况,由于x有很多因子,每一个因子都有方法数C(y + d - 1, d),则彼此之间累乘即可

在计算组合数时,因为最终结果都会模上MOD,所以可以将除法转化为快速幂求逆元

\(C^{m}_{n} = \frac{n!}{m!(n - m)!} = \frac{n(n - 1)(n - 2)...(n - m + 1)}{m!}\) 本题$n = y + d -1, m= d $

我们就可以将这里分子的除以m!转化为对m m - 1 ...1 求逆元再相乘 这就是快速幂求逆元的运用

#include<bits/stdc++.h>
#define int long long

using namespace std;

int T;
int x, y;

const int mod = 1e9 + 7;  //这里是1e9+7 刚开始写成10e9 debug半天

//快速幂求逆元
int qmi(int a, int b)
{
    int res = 1 % mod;
    while (b)
    {
        if (b & 1)
            res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    //cout << res << endl;
    return res;
}
void solve()
{
    cin >> x >> y;
    int ans = 1;
    
    //找出x的所有质因子 先筛因数的幂
    for (int i = 2; i * i <= x; i ++ )
    {
        int d = 0;
        if (x % i != 0) continue;
        while (x % i == 0)
        {
            x /= i;
            d ++ ;
        }

        //根据我们推导出的组合数公式求出每一个因子i的组合数
        int tmp = 1;
        for (int j = y; j <= d + y - 1; j ++ ) //分子的第一项n-m+1 就是y+d-1 - d + 1 = y 最后一项n就是y+d-1
        {
            tmp = tmp * j % mod; //先算分子
            tmp = tmp * qmi(j - y + 1, mod - 2) % mod; //除以分母 j - y + 1 也就是对应的1 2 ... m-1 m (m!)
            //cout << qmi(j - y + 1, mod - 2) << endl;
        }
        //cout << tmp << endl;
        ans = ans * tmp % mod; //最终的答案是每一个因子i的方案数相乘
    }
    
    //如果上面对x所有的因子筛完之后 x是一个不等于1的质数 他也是x的一个质因子 他的幂d=1 还要组合一次
    if (x > 1)
    {
        int d = 1, tmp = 1;
        for (int j = y; j <= d + y - 1; j ++ ) //分子的第一项n-m+1 就是y+d-1 - d + 1 = y 最后一项n就是y+d-1
        {
            tmp = tmp * j % mod; //先算分子
            tmp = tmp * qmi(j - y + 1, mod - 2) % mod; //除以分母 j - y + 1 也就是对应的1 2 ... m-1 m (m!)
        }
        ans = ans * tmp % mod;
        //cout << tmp << endl;
    }
    
    cout << ans << endl;
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    
    cin >> T;
    while (T -- )
    {
        solve();
    }

    return 0;
}
posted @ 2024-06-11 12:43  MsEEi  阅读(68)  评论(0)    收藏  举报