牛客練習賽52 題解

也許更好的閱讀體驗

本人只做出了前四題,所以只寫前四題的題解

A \mathcal{A\ 數數}

Description\mathcal{Description}

給出nn,求
i=1nj=1ni×j\begin{aligned}\sum_{i=1}^n\sum_{j=1}^ni\times j\end{aligned}i=1nj=1ni×j\begin{aligned}\prod_{i=1}^n\prod_{j=1}^ni\times j\end{aligned}
答案對998244353998244353取模

Solution\mathcal{Solution}

這是一道送分題
i=1nj=1ni×j=i=1ni×(j=1nj)=(j=1nj)×(i=1ni)=(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}
這個做多了題目就是一眼的事了
它的幾何性質是一個n×nn\times n的矩陣其所有子矩陣個數

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

不會推式子怎麼辦!
和博主一樣直接看吧!
第一個就不說了
第二個反正都是乘,我們直接考慮有多少個被乘了不就可以了嗎
考慮ii被乘的次數,在形如i×ji\times j這樣的情況每個都有ii,共有n+1n+1ii (i×ii\times i這樣的情況有兩個ii)
形如j×i(j!=i)j \times i(j!=i)的情況對於每個jj都會有,共有n1n-1個,所以ii會被乘2n2n

Code\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;
}

B Galahad\mathcal{B\ Galahad}

Description\mathcal{Description}

魔女要測試騎士的能力,要求他維護一個長度爲nn的序列,每次要詢問一個區間的和。

但是魔女覺得太簡單了,騎士能輕鬆記住nn個數的前綴和。

於是,魔女要求他回答一個區間的和,但如果某一個數在這個區間出現了多次,這個數只能被計算一次。

n,m500000,1ai500000n,m\leq 500000,1\leq a_i\leq 500000

Solution\mathcal{Solution}

這題好像是曾經的莫隊板子題,但是開大了數據範圍,於是咱的莫隊就TT
換個思路
先將查詢按照rr從小到大排序
lastilast_i表示數字ii上一次的出現位置
當我們找到一個數ii時,只要在當前位置加上ii,在lastilast_i減去ii即可保證不會重複計算
由於我們將查詢按照rr從小到大排序了
所以每個當前查詢一定經過現在這個位置
llastil\leq last_i的情況,我們已經將lastilast_i的答案抵消了,所以每個ii只會被計算一次
而現在實際上就是要求一段區間的加和,這個用樹狀數組維護就可以了

Code\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;
}

C \mathcal{C\ 烹飪}

Description\mathcal{Description}

YY上山拜師學藝,經過1010年之長的廚藝練習,已成爲當世名廚,今天他接受邀請,在衆人面前展示自己高超的廚藝。
人們給小YY提供了nn種食物,每種食物無限量供應,每種食物都有一個美味值,記爲 aia_i

YY爲了展示他的廚藝,他需要挑選出食材,使自己可以烹飪出任意正整數美味值的菜餚,初始時菜餚的美味值爲00,每次加入一種食材,他可以選擇讓菜餚的美味值上升aia_i,也可以選擇讓菜餚的美味值下降aia_i(或許最後會弄出來黑暗料理?)。

作爲當世名廚,小YY自然知道該怎麼挑選食材最佳。可是他並不知道有多少種最佳的挑選食材方案,於是他找到了你來幫忙。
我們使用無序數列(b1,b2,,bm)\left(b_1,b_2,\ldots,b_m\right)來表示從原來的nn種食材中挑選出了mm種食材,第ii種食材編號爲bib_i的方案。同時你需要注意,(1,2)\left(1,2\right)(2,1)\left(2,1\right)爲同一種方案,且當i!=ji!=jbi!=bjb_i != b_j
最佳的挑選食材方案指,挑選出 種食材mm種食材(1mn)(1\leq m\leq n),讓他們能夠組合出任意正整數美味值的菜餚。

答案對998244353998244353取模。

Solution\mathcal{Solution}

又是一個計數題
計數題有很多方法,dpdp,容斥,遞推等都可以計數
最開始我打算按照一定方式不重不漏的組合開始計算
但是想了半天除了幾個錯誤的方法仍然沒什麼思路
在想的時候發現按照一定方式不如直接算不合法的
於是考慮容斥
首先我們要知道一點
能組合出任意正整數\Leftrightarrow能組合出11
所以我們將目標鎖定在能否組合出11
合法的方法=所有方法-不能組合出11的方法
考慮哪些一定能組合出11
最顯然的是(a,a+1)(a,a+1)這樣的是合法的
而如果aa是個合數,設kkaa的一個因子
那麼(k,a+1)(k,a+1)也是合法的
可以想到任意a,ba,b只要滿足axby=1ax-by=1就合法
只要這個式子有解就合法ax+(by)=1ax+(-by)=1
而其有解條件是gcd(a,b)!=0gcd(a,b)!=0

其實換個思路也可以得到
只要是互質的就可以滿足條件,因爲它們之間一個對另一個取模的剩餘系是可以得到11
或者考慮只要是不互質的數就不可以滿足條件,因爲無論他們怎麼加減,結果都會有他們的最大公約數在裏面

於是可以知道,得要弄出這樣的序列a1,a2,,ana_1,a_2,\ldots,a_n,其滿足gcd(a1,a2,,an)=1gcd(a_1,a_2,\ldots,a_n)=1
求這樣的數列個數就可以考慮容斥了
gcd=1gcd=1的序列數==總序列數gcd !=1-gcd\ !=1的序列數
考慮枚舉它們的gcdgcd,令f[i]f[i]表示以ii作爲gcdgcd的序列數,那麼答案就是f[1]f[1]
f[i]=gcdf[i]=gcdii的倍數的序列數f[i2]f[i3]f[ik]-f[i*2]-f[i*3]-\ldots-f[i*k]
num[i]num[i]表示有多少個數是ii的倍數
gcdgcdii的倍數的序列數=2num[i]12^{num[i]}-1

2num[i]12^{num[i]}-1就是有num[i]num[i]個數可選可不選,但必須至少選一個的方案數

這樣這道題就得到解決了

Code\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;
}

D \mathcal{D\ 粉絲羣}

Description\mathcal{Description}

wjyyywjyyy粉絲羣中,除了wjywjy一共有nn個人,編號爲11nn

大家準備一起膜爆wjywjy,也就是說復讀2n2n消息,並且由於這nn個人在膜wjywjy上是統一的,所以每個人都必須至少復讀一次

wjywjy想要禁言掉一些人,但是這裏是wjywjy粉絲羣,不能隨便禁言膜wjywjy的人,於是wjywjy定下一個規則:

如果這nn個人,能夠分成兩組,使得兩個組中所有人的復讀次數的加和是相同的,那麼這nn個人都要被禁言。

nn個人開始討論,他們不想被暴政。

那麼問題來了,有多少種復讀的分配方法,使得wjyyywjyyy沒法把這nn個人分成滿足以上條件的兩組?

然而wjywjy的粉絲太多了,您只要輸出分配方法的種數以及這些分配方法中字典序第kk小的分配方式的異或和即可。

注意:如果有兩個人,則復讀次數分別爲(1,3)(1,3)與復讀次數分別爲(3,1)(3,1) 算兩種不同的分配方式。

Solution\mathcal{Solution}

這題打表找規律也可以看出來的
首先每人都至少復讀一次,就先記每人都復讀了一次

11 22 33 \ldots nn
11 11 11 \ldots 11

現在還剩nn次復讀要分給這些人,並且要使它們無法分成兩組復讀次數加和是一樣的

先考慮極端情況
若把這nn次機會再每人一次
那麼最終就是每人復讀兩次
nn爲奇數時,這樣是滿足條件的,而nn爲偶數是不滿足的
若把這nn次機會全部給一個人,那麼那個人就有n+1n+1次復讀
剩下的人加起來也不會超過他的復讀次數,所以全部給一個人也是滿足條件的

再考慮普遍的情況
若有些人沒有被分到復讀次數(即只復讀了一次)
我們把復讀次數大於11的人儘量平均分成兩組,兩組復讀次數分別爲x,y(x&lt;y)x,y(x&lt;y)
那麼一定有yxy-x個人是隻復讀了一次的
這裏可以自己手玩3,4,5,63,4,5,6這樣的看一看以便理解
把這些人放到xx那邊去,就剛好兩組平均了
所以這些都不滿足條件

綜上
只有把nn次復讀全部給一個人是絕對滿足的
nn爲奇數時所有人都復讀兩次也是可以的
而字典序肯定是(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)這樣子的
共有nn種滿足條件,當nn爲奇數時還要多一種(2,2,,2,2)(2,2,\ldots,2,2)
前面一堆11異或起來不是11就是00都是可以O(1)O(1)判的

Code\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)?2:n+1);
	else	printf("%lld\n%lld\n",n,(n+1)^1);
	return 0;
}

如有哪裏講得不是很明白或是有錯誤,歡迎指正
如您喜歡的話不妨點個贊收藏一下吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章