牛客练习赛52 题解

也许更好的阅读体验

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

A   数 数 \mathcal{A\ 数数} A 

D e s c r i p t i o n \mathcal{Description} Description

给出 n n n,求
∑ i = 1 n ∑ j = 1 n i × j \begin{aligned}\sum_{i=1}^n\sum_{j=1}^ni\times j\end{aligned} i=1nj=1ni×j ∏ i = 1 n ∏ j = 1 n i × j \begin{aligned}\prod_{i=1}^n\prod_{j=1}^ni\times j\end{aligned} i=1nj=1ni×j
答案对 998244353 998244353 998244353取模

S o l u t i o n \mathcal{Solution} Solution

这是一道送分题
∑ i = 1 n ∑ j = 1 n i × j = ∑ i = 1 n i × ( ∑ j = 1 n j ) = ( ∑ j = 1 n j ) × ( ∑ i = 1 n i ) = ( n ( n + 1 ) 2 ) 2 \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} i=1nj=1ni×j=i=1ni×(j=1nj)=(j=1nj)×(i=1ni)=(2n(n+1))2
这个做多了题目就是一眼的事了
它的几何性质是一个 n × n n\times n n×n的矩阵其所有子矩阵个数

∏ i = 1 n ∏ j = 1 n i × j = ∏ i = 1 n i n ∏ j = 1 n j = ( ∏ j = 1 n j ) n ∏ i = 1 n i n = ( ∏ j = 1 n j n ) ∏ i = 1 n i n = ∏ i = 1 n i 2 n = ( ∏ i = 1 n i ) 2 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=1nj=1ni×j=i=1ninj=1nj=(j=1nj)ni=1nin=(j=1njn)i=1nin=i=1ni2n=(i=1ni)2n

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

C o d e \mathcal{Code} Code

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年09月14日 星期六 19时06分48秒
*******************************/
#include 
#include 
#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;
}

B   G a l a h a d \mathcal{B\ Galahad} B Galahad

D e s c r i p t i o n \mathcal{Description} Description

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

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

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

n , m ≤ 500000 , 1 ≤ a i ≤ 500000 n,m\leq 500000,1\leq a_i\leq 500000 n,m500000,1ai500000

S o l u t i o n \mathcal{Solution} Solution

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

C o d e \mathcal{Code} Code

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年09月14日 星期六 19时18分27秒
*******************************/
#include 
#include 
#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;
}

C   烹 饪 \mathcal{C\ 烹饪} C 

D e s c r i p t i o n \mathcal{Description} Description

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

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

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

答案对 998244353 998244353 998244353取模。

S o l u t i o n \mathcal{Solution} Solution

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

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

于是可以知道,得要弄出这样的序列 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,,an,其满足 g c d ( a 1 , a 2 , … , a n ) = 1 gcd(a_1,a_2,\ldots,a_n)=1 gcd(a1,a2,,an)=1
求这样的数列个数就可以考虑容斥了
g c d = 1 gcd=1 gcd=1的序列数 = = =总序列数 − g c d   ! = 1 -gcd\ !=1 gcd !=1的序列数
考虑枚举它们的 g c d gcd gcd,令 f [ i ] f[i] f[i]表示以 i i i作为 g c d gcd gcd的序列数,那么答案就是 f [ 1 ] f[1] f[1]
f [ i ] = g c d f[i]=gcd f[i]=gcd i i i的倍数的序列数 − f [ i ∗ 2 ] − f [ i ∗ 3 ] − … − f [ i ∗ k ] -f[i*2]-f[i*3]-\ldots-f[i*k] f[i2]f[i3]f[ik]
n u m [ i ] num[i] num[i]表示有多少个数是 i i i的倍数
g c d gcd gcd i i i的倍数的序列数= 2 n u m [ i ] − 1 2^{num[i]}-1 2num[i]1

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

这样这道题就得到解决了

C o d e \mathcal{Code} Code

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年09月14日 星期六 19时33分10秒
*******************************/
#include 
#include 
#include 
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;
}

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

D e s c r i p t i o n \mathcal{Description} Description

w j y y y wjyyy wjyyy粉丝群中,除了 w j y wjy wjy一共有 n n n个人,编号为 1 1 1 n n n

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

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

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

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

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

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

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

S o l u t i o n \mathcal{Solution} Solution

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

1 1 1 2 2 2 3 3 3 … \ldots n n n
1 1 1 1 1 1 1 1 1 … \ldots 1 1 1

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

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

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

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

C o d e \mathcal{Code} Code

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年09月14日 星期六 20时36分37秒
*******************************/
#include 
#include 
#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)?2:n+1);
	else	printf("%lld\n%lld\n",n,(n+1)^1);
	return 0;
}

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

你可能感兴趣的:(思维题,计数问题,OIer做题记录)