其他分享
首页 > 其他分享> > 牛客练习赛52 题解

牛客练习赛52 题解

作者:互联网

也许更好的阅读体验

本人只做出了前四题,所以只写前四题的题解

\(\mathcal{A\ 数数}\)

\(\mathcal{Description}\)

给出\(n\),求
\(\begin{aligned}\sum_{i=1}^n\sum_{j=1}^ni\times j\end{aligned}\) 和 \(\begin{aligned}\prod_{i=1}^n\prod_{j=1}^ni\times j\end{aligned}\)
答案对\(998244353\)取模

\(\mathcal{Solution}\)

这是一道送分题
\(\begin{aligned}\sum_{i=1}^n\sum_{j=1}^ni\times j=\sum_{i=1}^ni\times\left(\sum_{j=1}^nj\right)=\left(\sum_{j=1}^nj\right)\times\left(\sum_{i=1}^ni\right)=\left(\frac{n(n+1)}{2}\right)^2\end{aligned}\)
这个做多了题目就是一眼的事了
它的几何性质是一个\(n\times n\)的矩阵其所有子矩阵个数

\(\begin{aligned}\prod_{i=1}^n\prod_{j=1}^ni\times j=\prod_{i=1}^ni^n\prod_{j=1}^nj = \left(\prod_{j=1}^nj\right)^n\prod_{i=1}^ni^n = \left(\prod_{j=1}^nj^n\right)\prod_{i=1}^ni^n = \prod_{i=1}^ni^{2n}=\left(\prod_{i=1}^ni\right)^{2n}\end{aligned}\)

不会推式子怎么办!
和博主一样直接看吧!
第一个就不说了
第二个反正都是乘,我们直接考虑有多少个被乘了不就可以了吗
考虑\(i\)被乘的次数,在形如\(i\times j\)这样的情况每个都有\(i\),共有\(n+1\)个\(i\) (\(i\times i\)这样的情况有两个\(i\))
形如\(j \times i(j!=i)\)的情况对于每个\(j\)都会有,共有\(n-1\)个,所以\(i\)会被乘\(2n\)次

\(\mathcal{Code}\)

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年09月14日 星期六 19时06分48秒
*******************************/
#include <cstdio>
#include <fstream>
#define ll long long
using namespace std;
const int maxn = 10000005;
const int mod = 998244353;
//{{{cin
struct IO{
    template<typename T>
    IO & operator>>(T&res){
        res=0;
        bool flag=false;
        char ch;
        while((ch=getchar())>'9'||ch<'0')   flag|=ch=='-';
        while(ch>='0'&&ch<='9') res=(res<<1)+(res<<3)+(ch^'0'),ch=getchar();
        if (flag)   res=~res+1;
        return *this;
    }
}cin;
//}}}
int T,n,ans1,ans2;
int s[maxn],mi[maxn];
//{{{ksm
int ksm (int a,int b)
{
    int s=1;
    for (;b;b>>=1,a=1ll*a*a%mod)
        if (b&1)    s=1ll*s*a%mod;
    return s;
}
//}}}
int main()
{
    s[1]=1;
    for (int i=2;i<=maxn-5;++i) s[i]=1ll*s[i-1]*i%mod;
    cin>>T;
    while (T--){
        cin>>n;
        ans1=1ll*n*(n+1)/2%mod;
        ans1=1ll*ans1*ans1%mod;
        ans2=ksm(s[n],2*n)%mod;
        printf("%d %d\n",ans1,ans2);
    }
    return 0;
}

\(\mathcal{B\ Galahad}\)

\(\mathcal{Description}\)

魔女要测试骑士的能力,要求他维护一个长度为\(n\)的序列,每次要询问一个区间的和。

但是魔女觉得太简单了,骑士能轻松记住\(n\)个数的前缀和。

于是,魔女要求他回答一个区间的和,但如果某一个数在这个区间出现了多次,这个数只能被计算一次。

\(n,m\leq 500000,1\leq a_i\leq 500000\)

\(\mathcal{Solution}\)

这题好像是曾经的莫队板子题,但是开大了数据范围,于是咱的莫队就\(T\)了
换个思路
先将查询按照\(r\)从小到大排序
令\(last_i\)表示数字\(i\)上一次的出现位置
当我们找到一个数\(i\)时,只要在当前位置加上\(i\),在\(last_i\)减去\(i\)即可保证不会重复计算
由于我们将查询按照\(r\)从小到大排序了
所以每个当前查询一定经过现在这个位置
而\(l\leq last_i\)的情况,我们已经将\(last_i\)的答案抵消了,所以每个\(i\)只会被计算一次
而现在实际上就是要求一段区间的加和,这个用树状数组维护就可以了

\(\mathcal{Code}\)

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年09月14日 星期六 19时18分27秒
*******************************/
#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
const int maxn = 1000006;
//{{{cin
struct IO{
    template<typename T>
        IO & operator>>(T&res){
            res=0;
            bool flag=false;
            char ch;
            while((ch=getchar())>'9'||ch<'0')   flag|=ch=='-';
            while(ch>='0'&&ch<='9') res=(res<<1)+(res<<3)+(ch^'0'),ch=getchar();
            if (flag)   res=~res+1;
            return *this;
        }
}cin;
//}}}
struct Q{
    int l,r,id;
}q[maxn];
int n,m;
int a[maxn],last[maxn];
ll c[maxn],ans[maxn];
bool cmp(Q a,Q b){  return a.r<b.r;}
//{{{query
ll query(int x)
{
    ll res=0;
    while(x>0){ res+=c[x];x-=x&-x;}
    return res;
}
//}}}
//{{{insert
void insert(int x,int v)
{
    while(x<=n){    c[x]+=v;x+=x&-x;}
}
//}}}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)   cin>>a[i];
    for(int i=1;i<=m;i++)   cin>>q[i].l>>q[i].r,q[i].id=i;
    sort(q+1,q+m+1,cmp);
    int t=1;
    for(int i=1;i<=n;i++){
        if(last[a[i]])  insert(last[a[i]],-a[i]);
        insert(i,a[i]);
        while(i==q[t].r)    ans[q[t].id]=query(q[t].r)-query(q[t].l-1),t++;
        last[a[i]]=i;
    }
    for(int i=1;i<=m;i++)   printf("%lld\n",ans[i]);
    return 0;
}

\(\mathcal{C\ 烹饪}\)

\(\mathcal{Description}\)

小 \(Y\)上山拜师学艺,经过\(10\)年之长的厨艺练习,已成为当世名厨,今天他接受邀请,在众人面前展示自己高超的厨艺。
人们给小\(Y\)提供了\(n\)种食物,每种食物无限量供应,每种食物都有一个美味值,记为 \(a_i\)。

小\(Y\)为了展示他的厨艺,他需要挑选出食材,使自己可以烹饪出任意正整数美味值的菜肴,初始时菜肴的美味值为\(0\),每次加入一种食材,他可以选择让菜肴的美味值上升\(a_i\),也可以选择让菜肴的美味值下降\(a_i\)(或许最后会弄出来黑暗料理?)。

作为当世名厨,小\(Y\)自然知道该怎么挑选食材最佳。可是他并不知道有多少种最佳的挑选食材方案,于是他找到了你来帮忙。
我们使用无序数列\(\left(b_1,b_2,\ldots,b_m\right)\)来表示从原来的\(n\)种食材中挑选出了\(m\)种食材,第\(i\)种食材编号为\(b_i\)的方案。同时你需要注意,\(\left(1,2\right)\)和\(\left(2,1\right)\)为同一种方案,且当\(i!=j\)时\(b_i != b_j\)。
最佳的挑选食材方案指,挑选出 种食材\(m\)种食材\((1\leq m\leq n)\),让他们能够组合出任意正整数美味值的菜肴。

答案对\(998244353\)取模。

\(\mathcal{Solution}\)

又是一个计数题
计数题有很多方法,\(dp\),容斥,递推等都可以计数
最开始我打算按照一定方式不重不漏的组合开始计算
但是想了半天除了几个错误的方法仍然没什么思路
在想的时候发现按照一定方式不如直接算不合法的
于是考虑容斥
首先我们要知道一点
能组合出任意正整数\(\Leftrightarrow\)能组合出\(1\)
所以我们将目标锁定在能否组合出\(1\)
合法的方法=所有方法-不能组合出\(1\)的方法
考虑哪些一定能组合出\(1\)
最显然的是\((a,a+1)\)这样的是合法的
而如果\(a\)是个合数,设\(k\)为\(a\)的一个因子
那么\((k,a+1)\)也是合法的
可以想到任意\(a,b\)只要满足\(ax-by=1\)就合法
只要这个式子有解就合法\(ax+(-by)=1\)
而其有解条件是\(gcd(a,b)!=0\)

其实换个思路也可以得到
只要是互质的就可以满足条件,因为它们之间一个对另一个取模的剩余系是可以得到\(1\)的
或者考虑只要是不互质的数就不可以满足条件,因为无论他们怎么加减,结果都会有他们的最大公约数在里面

于是可以知道,得要弄出这样的序列\(a_1,a_2,\ldots,a_n\),其满足\(gcd(a_1,a_2,\ldots,a_n)=1\)
求这样的数列个数就可以考虑容斥了
\(gcd=1\)的序列数\(=\)总序列数\(-gcd\ !=1\)的序列数
考虑枚举它们的\(gcd\),令\(f[i]\)表示以\(i\)作为\(gcd\)的序列数,那么答案就是\(f[1]\)
而\(f[i]=gcd\)是\(i\)的倍数的序列数\(-f[i*2]-f[i*3]-\ldots-f[i*k]\)
用\(num[i]\)表示有多少个数是\(i\)的倍数
\(gcd\)是\(i\)的倍数的序列数=\(2^{num[i]}-1\)

\(2^{num[i]}-1\)就是有\(num[i]\)个数可选可不选,但必须至少选一个的方案数

这样这道题就得到解决了

\(\mathcal{Code}\)

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年09月14日 星期六 19时33分10秒
*******************************/
#include <cstdio>
#include <fstream>
#include <algorithm>
using namespace std;
const int maxn = 3005;
const int mx = 2000;
const int mod = 998244353;
//{{{cin
struct IO{
    template<typename T>
    IO & operator>>(T&res){
        res=0;
        bool flag=false;
        char ch;
        while((ch=getchar())>'9'||ch<'0')   flag|=ch=='-';
        while(ch>='0'&&ch<='9') res=(res<<1)+(res<<3)+(ch^'0'),ch=getchar();
        if (flag)   res=~res+1;
        return *this;
    }
}cin;
//}}}
int n;
int a[maxn],num[maxn],f[maxn],s[maxn],mi[maxn];
void add (int &x,int y){    x=((x+y)%mod+mod)%mod;}
//{{{gcd
int gcd (int a,int b)
{
    if (!b) return a;
    return gcd(b,a%b);
}
//}}}
int main()
{
    mi[0]=1;
    cin>>n;
    for (int i=1;i<=n;++i)  cin>>a[i];
    for (int i=1;i<=3000;++i)   mi[i]=2ll*mi[i-1]%mod;
    for (int i=mx;i>=1;--i){
        for (int j=1;j<=n;++j)
            if (a[j]%i==0)  ++num[i];
        f[i]=(mi[num[i]]+mod-1)%mod;
        for (int j=2;i*j<=mx;++j)   add(f[i],-f[i*j]);
    }
    printf("%d\n",f[1]);
    return 0;
}

\(\mathcal{D\ 粉丝群}\)

\(\mathcal{Description}\)

在\(wjyyy\)粉丝群中,除了\(wjy\)一共有\(n\)个人,编号为\(1\)到\(n\)。

大家准备一起膜爆\(wjy\),也就是说复读\(2n\)次消息,并且由于这\(n\)个人在膜\(wjy\)上是统一的,所以每个人都必须至少复读一次

\(wjy\)想要禁言掉一些人,但是这里是\(wjy\)粉丝群,不能随便禁言膜\(wjy\)的人,于是\(wjy\)定下一个规则:

如果这\(n\)个人,能够分成两组,使得两个组中所有人的复读次数的加和是相同的,那么这\(n\)个人都要被禁言。

这\(n\)个人开始讨论,他们不想被暴政。

那么问题来了,有多少种复读的分配方法,使得\(wjyyy\)没法把这\(n\)个人分成满足以上条件的两组?

然而\(wjy\)的粉丝太多了,您只要输出分配方法的种数以及这些分配方法中字典序第\(k\)小的分配方式的异或和即可。

注意:如果有两个人,则复读次数分别为\((1,3)\)与复读次数分别为\((3,1)\) 算两种不同的分配方式。

\(\mathcal{Solution}\)

这题打表找规律也可以看出来的
首先每人都至少复读一次,就先记每人都复读了一次

\(1\) \(2\) \(3\) \(\ldots\) \(n\)
\(1\) \(1\) \(1\) \(\ldots\) \(1\)

现在还剩\(n\)次复读要分给这些人,并且要使它们无法分成两组复读次数加和是一样的

先考虑极端情况
若把这\(n\)次机会再每人一次
那么最终就是每人复读两次
当\(n\)为奇数时,这样是满足条件的,而\(n\)为偶数是不满足的
若把这\(n\)次机会全部给一个人,那么那个人就有\(n+1\)次复读
剩下的人加起来也不会超过他的复读次数,所以全部给一个人也是满足条件的

再考虑普遍的情况
若有些人没有被分到复读次数(即只复读了一次)
我们把复读次数大于\(1\)的人尽量平均分成两组,两组复读次数分别为\(x,y(x<y)\)
那么一定有\(y-x\)个人是只复读了一次的
这里可以自己手玩\(3,4,5,6\)这样的看一看以便理解
把这些人放到\(x\)那边去,就刚好两组平均了
所以这些都不满足条件

综上
只有把\(n\)次复读全部给一个人是绝对满足的
当\(n\)为奇数时所有人都复读两次也是可以的
而字典序肯定是\((1,1,\ldots1,n+1),(1,1,\ldots,n+1,1)\ldots(n+1,1,\ldots,1,1)\)这样子的
共有\(n\)种满足条件,当\(n\)为奇数时最后还要多一种\((2,2,\ldots,2,2)\)
前面一堆\(1\)异或起来不是\(1\)就是\(0\)都是可以\(O(1)\)判的

\(\mathcal{Code}\)

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年09月14日 星期六 20时36分37秒
*******************************/
#include <cstdio>
#include <fstream>
#define ll long long
using namespace std;
//{{{cin
struct IO{
    template<typename T>
    IO & operator>>(T&res){
        res=0;
        bool flag=false;
        char ch;
        while((ch=getchar())>'9'||ch<'0')   flag|=ch=='-';
        while(ch>='0'&&ch<='9') res=(res<<1)+(res<<3)+(ch^'0'),ch=getchar();
        if (flag)   res=~res+1;
        return *this;
    }
}cin;
//}}}
ll n,k;
int main()
{
    cin>>n>>k;
    if (n==1){  printf("1\n2\n");return 0;}
    if (n&1)    printf("%lld\n%lld\n",n+1,(k==n+1)?2:n+1);
    else    printf("%lld\n%lld\n",n,(n+1)^1);
    return 0;
}

如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧

标签:ni,练习赛,int,题解,牛客,ch,mathcal,prod,复读
来源: https://www.cnblogs.com/Morning-Glory/p/11521114.html