Codeforces Round 925 (Div. 3)(A,B,C,D,E,F,G)

比赛链接

这场打的很顺,感觉难度和 div 4 差不多,不是很难。D题稍微考了考同余的性质,E题直接模拟过程即可,F题也可以暴力模拟或者拓扑排序,G题是个数学题,是个简单隔板法。A到F题都可以直接模拟就有点离谱。


A. Recovering a Small String

题意:

尼基塔的单词正好由 3 3 3 个小写拉丁字母组成。拉丁字母的编号从 1 1 1 26 26 26 ,其中字母 "a "的编号为 1 1 1 ,字母 "z "的编号为 26 26 26

他将这个单词编码为字母表中所有字符的位置总和。例如,他将单词 "cat "编码为整数 3 + 1 + 20 = 24 3 + 1 + 20 = 24 3+1+20=24 ,因为字母 "c "在字母表中的索引为 3 3 3 ,字母 "a "的索引为 1 1 1 ,字母 "t "的索引为 20 20 20

然而,这种编码结果是模糊的!例如,对单词 "ava "进行编码时,也会得到整数 1 + 22 + 1 = 24 1 + 22 + 1 = 24 1+22+1=24

请找出由 3 3 3 个字母组成的字典序最小的单词。

当且仅当以下条件之一成立时,字符串 a a a 在词法上小于字符串 b b b

  • a a a b b b 的前缀,但 a ≠ b a \ne b a=b
  • a a a b b b 不同的第一个位置,字符串 a a a 的字母在字母表中出现的时间早于 b b b 中的相应字母。

思路:

显然先动后面的字母,大的字母放在后面。初始是aaa,先第三个位置上从a到z,这时 n ∈ [ 3 , 28 ] n\in[3,28] n[3,28],然后第二个位置从a到z,这时 n ∈ [ 29 , 53 ] n\in[29,53] n[29,53],最后第一个位置从a到z,这时 n ∈ [ 54 , 78 ] n\in[54,78] n[54,78]

也可以直接dfs爆搜,毕竟就三个位置。

code:

#include <iostream>
#include <cstdio>
using namespace std;

int T,n;

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		if(n<=28)printf("aa%c\n",n-2-1+'a');
		else if(n<=53)printf("a%cz\n",n-1-26-1+'a');
		else printf("%czz\n",n-26-26-1+'a');
	}
	return 0;
}

B. Make Equal

题意:

n n n 个盛水的容器一字排开,从左到右依次编号为 1 1 1 n n n 。每个容器可以装任意数量的水;最初,第 i i i 个容器装有 a i a_i ai 个单位的水。 a i a_i ai 的和能被 n n n 整除。

你可以任意(可能是零)次地执行下面的操作:从第 i i i 个容器中倒入任意数量的水到第 j j j 个容器中,其中 i i i 必须小于 j j j (即 i < j i\lt j i<j )。任何下标都可以被选择为 i i i j j j ,次数不限。

请判断是否有可能通过这种操作使所有容器中的水量相同。

思路:

可以任意选择两个位置,并把前面的位置的容器的水向后倒任意数量的水,所以只要前面的容器水量够用,就可以倒给后面的容器,使得它们的水量相同。反之就没有办法相同。

换句话说,假设平均水量为 a v e ave ave,如果 i − 1 i-1 i1 个位置的总水量大于等于 a v e ∗ ( i − 1 ) ave*(i-1) ave(i1),那么前 i − 1 i-1 i1 个位置就可以向第 i i i 个位置贡献水量,还得有足够的水量把它变成 a v e ave ave 水量,这样 i i i 个位置的总水量大于等于 a v e ∗ i ave*i avei。否则第 i i i 个位置永远不可能达到平均水量。所以直接枚举每一个 i i i 看看它的前缀和水量是否超过了 a v e ∗ i ave*i avei,否则就会导致某个位置达不到平均水量。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=2e5+5;
typedef long long ll;

int T,n,a[maxn];

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		ll tot=0,ave;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			tot+=a[i];
		}
		ave=tot/n;
		ll res=0;
		bool f=true;
		for(int i=1;i<=n;i++){
			res+=a[i];
			if(res<ave*i){
				f=false;
				break;
			}
		}
		puts((f)?"YES":"NO");
	}
	return 0;
}

C. Make Equal Again

题意:

你有一个由 n n n 个整数组成的数组 a a a

您可以至多一次进行以下操作:选择三个整数 i i i , j j j , x x x ( 1 ≤ i ≤ j ≤ n 1 \le i \le j \le n 1ijn ),并将数组中索引为 i i i j j j 的所有元素赋值为 x x x 。这个操作的代价取决于所选的索引,等于 ( j − i + 1 ) (j - i + 1) (ji+1)

例如,数组等于 [ 1 , 2 , 3 , 4 , 5 , 1 ] [1, 2, 3, 4, 5, 1] [1,2,3,4,5,1] 。如果我们选择 i = 2 , j = 4 , x = 8 i = 2, j = 4, x = 8 i=2,j=4,x=8 ,那么执行此操作后,数组将等于 [ 1 , 8 , 8 , 8 , 5 , 1 ] [1, 8, 8, 8, 5, 1] [1,8,8,8,5,1]

要使数组中的所有元素相等,最少需要花费多少布尔值?

思路:

因为只能进行一次操作,还要让所有元素相同,那么就要把中间所有不同的数字全部换掉。具体来说,最左边和最右边可能有两种数字,考虑把从左边第一个不同的数字开始把后面换成最左边的数字,以及把从右边第一个不同的数字开始把前面换成最右边的数字,两种情况都算一遍然后取最小值即可。特殊情况就是左右两边数字相同,这时只换掉中间的数即可。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=2e5+5;

int T,n,a[maxn];

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++)
			cin>>a[i];
		
		int l=1,r=n,ans=1145141919;
		while(l<n && a[l+1]==a[l])l++;
		while(r>1 && a[r-1]==a[r])r--;
		if(a[1]==a[n])ans=(r>l)?r-l-1:0;
		else ans=min(n-l,r-1);
		
		cout<<ans<<endl;
	}
	return 0;
}

D. Divisible Pairs

题意:

波利卡普有两个最喜欢的整数 x x x y y y (它们可以相等),他找到了一个长度为 n n n 的数组 a a a

如果出现以下情况,波利卡普认为一对索引 ⟨ i , j ⟩ \langle i, j \rangle i,j ( 1 ≤ i < j ≤ n 1 \le i \lt j \le n 1i<jn ) 很美:

  • a i + a j a_i + a_j ai+aj 可以被 x x x 整除;
  • a i − a j a_i - a_j aiaj 能被 y y y 整除。

例如,如果 x = 5 x=5 x=5 , y = 2 y=2 y=2 , n = 6 n=6 n=6 , a = a= a= [ 1 , 2 , 7 , 4 , 9 , 6 1, 2, 7, 4, 9, 6 1,2,7,4,9,6 ],那么唯一美丽的一对是:

  • ⟨ 1 , 5 ⟩ \langle 1, 5 \rangle 1,5 : a 1 + a 5 = 1 + 9 = 10 a_1 + a_5 = 1 + 9 = 10 a1+a5=1+9=10 10 10 10 能被 5 5 5 整除)和 a 1 − a 5 = 1 − 9 = − 8 a_1 - a_5 = 1 - 9 = -8 a1a5=19=8 ( − 8 -8 8 能被 2 2 2 整除);
  • ⟨ 4 , 6 ⟩ \langle 4, 6 \rangle 4,6 : a 4 + a 6 = 4 + 6 = 10 a_4 + a_6 = 4 + 6 = 10 a4+a6=4+6=10 10 10 10 能被 5 5 5 整除)和 a 4 − a 6 = 4 − 6 = − 2 a_4 - a_6 = 4 - 6 = -2 a4a6=46=2 ( − 2 -2 2 能被 2 2 2 整除)。

求数组 a a a 中优美对的个数。

思路:

同余的性质,相加能被x整除,那么先同余x再相加同样能被x整除,相减被y整除,那么先同余y再相减同样能被y整除(其实相当于就是两者余数相同)

所以对一个数,它同余x和同余y的余数其实已经确定了,其实就是找和它配成同余x的余数相加能被x整除以及同余y的余数相同的数前面有多少个。因为xy同样可以很大,开桶会爆空间,所以使用map或者set。

考虑使用map<pair<int,int>,int> ,前面的pair键描述分别同余x和y的余数信息,后面的int值记录数量即可。

code:

#include <iostream>
#include <cstdio>
#include <map>
using namespace std;

int T,n,x,y;

int main(){
	cin>>T;
	while(T--){
		cin>>n>>x>>y;
		map<pair<int,int>,int> mp;//同余x y的信息,个数 
		long long ans=0;
		for(int i=1,t;i<=n;i++){
			cin>>t;
			ans+=mp[make_pair((x-t%x)%x,t%y)];
			
			mp[make_pair(t%x,t%y)]++;
		}
		cout<<ans<<endl;
	}
	return 0;
}

E. Anna and the Valentine’s Day Gift

题意:

情人节那天,萨沙给了安娜一张 n n n 个整数的列表 a a a。安娜不需要这个列表,所以她建议通过玩游戏来销毁它。

玩家轮流玩。萨沙是个绅士,所以他让安娜先下手为强。

  • 轮到安娜时,她必须从列表中选择一个元素 a i a_i ai ,并颠倒其数字顺序。例如,如果安娜选择了数值为 42 42 42 的元素,它就会变成 24 24 24 ;如果安娜选择了数值为 1580 1580 1580 的元素,它就会变成 851 851 851 。注意,前导零会被去掉。在这一轮之后,列表中元素的数量不会改变。
  • 轮到萨沙时,他必须从列表中提取两个元素 a i a_i ai a j a_j aj ( i ≠ j i \ne j i=j ),以任意顺序将它们连接起来,并将结果插回列表。例如,如果萨沙选择了等于 2007 2007 2007 19 19 19 的元素,他将从列表中删除这两个元素,并添加整数 200719 200719 200719 192007 192007 192007 。这样转一圈后,列表中的元素数会减少 1 1 1

玩家不能跳过回合。当列表中只剩下一个整数时,游戏结束。如果这个整数不小于 1 0 m 10^m 10m (即 ≥ 1 0 m \ge 10^m 10m ),萨沙获胜。否则,安娜获胜。

由此可以看出,游戏总会结束。如果双方都以最佳方式下棋,谁会获胜?

思路:

模拟一下游玩过程很快就会发现,其实我们对这个数具体是什么根本不关心,Anna只关心能不能利用去除前导零的规则削减数位,Sasha只关心能不能利用合并规则来保护后导零不被变成前导零(要保护的数放在任意一个数前面即可)。因为 1 0 m 10^m 10m m + 1 m+1 m+1 位数中最小的那个数,所以只要你最后合并出来的数数位个数大于等于 m + 1 m+1 m+1,那么这个数就一定大于等于 1 0 m 10^m 10m,不需要关心具体合并出来一个什么东西。

模拟游玩过程,一开始Anna一定会取出当前 后导零最多的数,将它翻转,这时总的数位会损失后导零个数个数位(因为所有数都不为0,所以不用考虑0这种特殊情况)。然后Sasha会取出当前 后导零最多的数,将它保护起来,这时这个数的后导零就消失了,总位数就会累加到答案里,同理后面的步骤。

所以可以直接暴力模拟这个过程。使用set<pair<int,int> >来存储一个数,前一位表示后导零位数,后一位表示这个数的位数。每次先取出当前后导零数位个数最大的数,后导零位数归零,位数减去损失的位数,放回,然后再取出当前后导零数位个数最大的数,直接将位数加入到答案即可。不过要注意的是,数是可以重复的,后导零位数和这个数的位数是有可能重复的,但是set不能存储重复信息,所以可以多加一维,用来存储没啥特殊含义的数,用来区分它们。

code:

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;

int T,n,m;

pair<int,int> g(int x){//后导0 总位数 
	int cnt1=0,cnt2;
	while(x%10==0){
		cnt1++;
		x/=10;
	}
	cnt2=cnt1;
	while(x){
		cnt2++;
		x/=10;
	}
	return make_pair(cnt1,cnt2);
}

int main(){
	cin>>T;
	while(T--){
		cin>>n>>m;
		set<pair<pair<int,int>,int> > S;
		for(int i=1,t;i<=n;i++){
			cin>>t;
			S.insert(make_pair(g(t),i));
		}
		
		int ans=0;
		while(!S.empty()){
			pair<pair<int,int>,int> x=*S.rbegin();
			S.erase(x);
			x.first.second-=x.first.first;
			x.first.first=0;
			S.insert(x);
			
			x=*S.rbegin();
			S.erase(x);
			ans+=x.first.second;
		}
		puts((ans>m)?"Sasha":"Anna");
	}
	return 0;
}

其实这么写还是比较差强人意的,太麻烦了。考虑到一个数后导零被删掉之后总位数就不会再变化了,之后Anna选不选其实无所谓,Anna能消除的只有后导零位数最大的,第三大的,第五大的…所以我们记录一下总位数,然后把所有后导零位数拿出来排个序,奇数位上的后导零位数可以被Anna消除掉,就在总位数上减掉这一部分,剩下的就是答案数字的位数。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=2e5+5;

int T,n,m,a[maxn];

pair<int,int> g(int x){
	int cnt1=0,cnt2;
	while(x%10==0){
		cnt1++;
		x/=10;
	}
	cnt2=cnt1;
	while(x){
		cnt2++;
		x/=10;
	}
	return make_pair(cnt1,cnt2);
}

int main(){
	cin>>T;
	while(T--){
		cin>>n>>m;
		int ans=0;
		for(int i=1,t;i<=n;i++){
			cin>>t;
			pair<int,int> tmp(g(t));
			a[i]=tmp.first;
			ans+=tmp.second;
		}
		sort(a+1,a+n+1,greater<int>());
		for(int i=1;i<=n;i+=2)
			ans-=a[i];
		
		puts((ans>m)?"Sasha":"Anna");
	}
	return 0;
}

F. Chat Screenshots

题意:

编程竞赛聊天室有 n n n 人。聊天参与者按活动排序,但每个人都把自己排在最前面。

例如,有 4 4 4 人参加聊天,他们的顺序是 [ 2 , 3 , 1 , 4 ] [2, 3, 1, 4] [2,3,1,4] 。那么

  • 1 1 1 个用户看到的顺序是 [ 1 , 2 , 3 , 4 ] [1, 2, 3, 4] [1,2,3,4]
  • 2 2 2 个用户看到的顺序是 [ 2 , 3 , 1 , 4 ] [2, 3, 1, 4] [2,3,1,4]
  • 3 3 3 个用户看到的顺序是 [ 3 , 2 , 1 , 4 ] [3, 2, 1, 4] [3,2,1,4]
  • 4 4 4 个用户看到的顺序是 [ 4 , 2 , 3 , 1 ] [4, 2, 3, 1] [4,2,3,1]

k k k 人在聊天中发布了截图,显示了该用户看到的参与者顺序。这些截图是在很短的时间内截取的,参与者的顺序并没有改变。

你的任务是确定所有截图是否都有一定的顺序。

思路1:

这题可以直接模拟,也可以拓扑排序,用拓扑排序很好写,甚至还看到用树状数组的。

题目上非常好心的保证了 每个截图都是一个排列,而且 k k k 个截图的开头的数字各不相同

而且手玩一下数据的话其实不难发现一般只要两组截图就可以推理出来原序列。具体来说,如果两个截图表示的原序列是一致的,那么就会出现下面这种情况(以题目第三组样例举例):
在这里插入图片描述
两个序列从 2 ∼ n 2\sim n 2n 第一个出现不同的位置 i i i 上 其中一个序列上的数一定是另一个序列的开头数字。这时这个序列第 i i i 个位置就填补上了另一个序列的漏洞,这个序列第 2 ∼ i 2\sim i 2i 和另一个序列 i ∼ n i\sim n in 组合起来就是原序列。

不过想法很美好,但是会出现特例。当这两个数在原序列中紧挨在一起时,就没有办法区分了,比如第九个样例前两个序列:

3 5 1 4 2
2 5 1 4 3

这时候可以推出来两种可行的情况:

5 1 4 3 2
5 1 4 2 3

不过也就仅此而已了,出现这种特例的原因是两者紧挨,导致另一个数被提取到前方时两者中间没有标识前后位置的数。实际上可以直接特判这种情况,然后两种情况都验证一遍。出现这种情况时,两个序列从 2 ∼ n 2\sim n 2n 第一个出现不同的位置 i i i 上 其中一个序列上的数一定是另一个序列的开头数字,反过来,另一个序列上的数也一定是这个序列的开头数字。

找到原序列后就可以对 k k k 个截图进行一一验证,前两个用来构造的截图也要验证,因为不能保证前两个截图的原序列是相同的,这时候构造出来的原序列是有问题的,不过没有关系,在验证前两个序列的时候一定会出错。

n ∗ k ≤ 2 ∗ 1 0 5 n*k\le 2*10^5 nk2105,但是 n , k ≤ 2 ∗ 1 0 5 n,k\le 2*10^5 n,k2105,所以需要动态开空间。

code1:

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn=2e5+5;

int T,n,k;
int t[maxn],t1[maxn];

int main(){
	cin>>T;
	while(T--){
		cin>>n>>k;
		vector<vector<int> > a(k+1,vector<int>(n+1));
		for(int i=1;i<=k;i++)
			for(int j=1;j<=n;j++)
				cin>>a[i][j];
		if(k==1){
			puts("YES");
			continue;
		}
		bool f=true,flag=false;
		for(int i=2;i<=n;i++){
			if(a[1][i]!=a[2][i]){
			
				if(a[1][i]==a[2][1]){
					if(a[2][i]==a[1][1]){
						flag=true;
						for(int j=2;j<=i;j++)
							t1[j-1]=a[2][j];
						for(int j=i;j<=n;j++)
							t1[j]=a[1][j];
					}
					for(int j=2;j<=i;j++)
						t[j-1]=a[1][j];
					for(int j=i;j<=n;j++)
						t[j]=a[2][j];
				}
				else if(a[2][i]==a[1][1]){
					for(int j=2;j<=i;j++)
						t[j-1]=a[2][j];
					for(int j=i;j<=n;j++)
						t[j]=a[1][j];
				}
				else {
					f=false;
				}
				
				break;
			}
		}
		if(!f){
			puts("NO");
			continue;
		}
		
		int idx;
		for(int i=1;i<=k;i++){
			idx=1;
			for(int j=2;j<=n;j++){
				if(a[i][1]==t[idx])idx++;
				if(a[i][j]==t[idx])idx++;
				else f=false;
			}
		}
		
		if(!f && flag){//打复活赛 
//			cout<<"想你了牢大"<<endl;
			f=true;
			for(int i=1;i<=k;i++){
				idx=1;
				for(int j=2;j<=n;j++){
					if(a[i][1]==t1[idx])idx++;
					if(a[i][j]==t1[idx])idx++;
					else f=false;
				}
			}
		}
		
		puts((f)?"YES":"NO");
	}
	return 0;
}

思路2:

由于每一个截图相当于确定了 2 ∼ n 2\sim n 2n 位置上的 n − 1 n-1 n1 个数的相对位置,这个相对位置是不会变化的,比如前面有个截图说2在3前面,这时如果后面有个截图说3在2前面,这时候肯定就矛盾了,所以2在3前面的事实就固定了,是一个拓扑序,所以可以使用拓扑排列。如果没法拓扑排列,说明成环了,这时就会出现矛盾。

或者换种思路,原本的顺序应该是一个从前到后的链一样的关系,所有的截图都满足这个顺序,所以应当是没有环的。如果有一个截图遵循另一个顺序,就会导致至少有一对数发生了交换,这时候从i到j的路径就会以从j到i的路径出现,也就是成环了。这个东西就可以拓扑排序来做。

code2:

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
const int maxn=2e5+5;

int T,n,k;

int head[maxn],d[maxn],cnt;
struct edge{
	int v,nxt;
}e[maxn<<1];
void add(int u,int v){
	d[v]++;
	e[++cnt].v=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}

int main(){
	cin>>T;
	while(T--){
		cin>>n>>k;
		for(int i=1;i<=n;i++)head[i]=d[i]=0;
		cnt=0;
		for(int i=1,u,v;i<=k;i++){
			for(int j=1;j<=n;j++){
				cin>>v;
				if(j>2)add(u,v);
				u=v;
			}
		}
		
		queue<int> q;
		int counter=0;
		for(int i=1;i<=n;i++)
			if(!d[i]){
				q.push(i);
				counter++;
			}
		
		while(!q.empty()){
			int u=q.front();
			q.pop();
			for(int i=head[u],v;i;i=e[i].nxt){
				v=e[i].v;
				d[v]--;
				if(!d[v]){
					q.push(v);
					counter++;
				}
			}
		}
		puts((counter==n)?"YES":"NO");
	}
	return 0;
}

思路3:

无意之间翻到的一个解法,也挺有意思的。

如果我们对第一个序列的 2 ∼ n 2\sim n 2n 上的数重新编号,其实后面的验证相当于看有没有逆序对,如果出现逆序对就错了。另外我们还要第一个序列开头数字的可能位置并进行验证,可以在验证后面的截图时看后面第一个序列开头数字的位置来找到它在第几个数和第几个数之间,如果多个截图冲突了,就说明无解。

其实树状数组实现的功能就是找逆序对。

code3:

某个大佬的解法


G. One-Dimensional Puzzle

题意:

您有一个一维拼图,所有拼图都需要排成一行,并相互连接。所有拼图元素都是完全白色的,只有当它们的形状不同时才能相互区分。

每个元素的顶部和底部都有直线边框,左右两侧都有连接点,每个连接点都可以是一个突出物或一个凹槽。不能旋转拼图。

可以看出,拼图的类型正好有 4 4 4 种。如果左边元素的右边连接点与右边元素的左边连接点相对,那么两个元素就可以连接起来。

所有可能的拼图类型。

谜题中每种类型包含 c 1 , c 2 , c 3 , c 4 c_1, c_2, c_3, c_4 c1,c2,c3,c4 个元素。如果您成功地将所有拼图组合成一条长链,那么谜题就算完成了。您想知道有多少种方法可以做到这一点。

思路:

开始之前 先讲隔板法吧。隔板法其实有两种隔板法,一种是解决 不同相同不能空盒 的组合问题,一种是解决 不同相同空盒 的组合问题(这里相同表示互不区分)。

如果我们要把 n n n 个相同的球放入到 m m m 个相同的盒子中,不能空盒,那么可以先把球一字排开, n n n 个球之间有 n − 1 n-1 n1 个空隙,我们选取其中的 m − 1 m-1 m1 个位置放入隔板,这时 m − 1 m-1 m1 个隔板就将 n n n 个球分成了 m m m 段不为空的区间,我们按顺序把每个区间的球放入盒子即可。答案就是 C n − 1 m − 1 = C n − 1 n − m C_{n-1}^{m-1}=C_{n-1}^{n-m} Cn1m1=Cn1nm

如果我们要把 n n n 个相同的球放入到 m m m 个相同的盒子中,能空盒,那么可以先留出 n + m − 1 n+m-1 n+m1 个位置,先在这 n + m − 1 n+m-1 n+m1 个位置放入 m − 1 m-1 m1 个隔板,然后把球以此放入空位置上,这时 m − 1 m-1 m1 个隔板就将 n n n 个球分成了 m m m 段可空的区间,我们按顺序把每个区间的球放入盒子即可。答案就是 C n + m − 1 m − 1 = C n + m − 1 n C_{n+m-1}^{m-1}=C_{n+m-1}^n Cn+m1m1=Cn+m1n

上面两种情形的区别只有能不能空盒,实际上,这两种问题是可以相互转化的。对n个相同球,m个不同盒,能空盒的一种合法情况中,可以给每一个盒子里放入一个球,这样问题就转化成了n+m个相同球,m个不同盒,不能空盒的一种合法情况。相反,对n个相同球,m个不同盒,不能空盒的一种合法情况中,可以给每一个盒子里拿出一个球,这样问题就转化成了n-m个相同球,m个不同盒,不能空盒的一种合法情况。这两种情况是一一映射的,方法数是相同的。如果你把 n = n + m n=n+m n=n+m 代入到不能空盒的答案 C n − 1 m − 1 C_{n-1}^{m-1} Cn1m1 中,就会得到 C n + m − 1 m − 1 C_{n+m-1}^{m-1} Cn+m1m1,正好就是下面能空盒的答案。

–分割线–

然后回到这个题,发现34号拼图是比较万金油的拼图,如果前面是突起,那么我们就可以在后面接上任意个3号拼图,最后面仍然保持突起,同理4号拼图。

那么我们先不管3和4,只看1和2,发现只能是1和2交替相接,然后1 2之间可以插入3,2 1之间可以插入4。这样的话,1和2的个数最多只能相差1。如果12个数相差为1,那么多的那个放在奇数位上,少的那个放在偶数位上。如果两者数量相同,那么谁放奇数位谁放偶数位都可以,这时有两种方案。不管怎么说,如果只有1 2的话,摆放方案都是固定的。

如果有3 4的话,3可以放在任意的1 2之间的空隙,可以放任意个,如果把1 2之间的空隙看做一个盒子,3看做球,那么就转化成了上面说的 不同盒相同球能空盒 的组合问题了,直接隔板法可以算。两头可以看做是多接了一个虚拟的1或2,这个空隙也要算。比如1 2,他可以看做是2 1 2 1,前面有个虚拟的2,后面有个虚拟的1,这样就有两个2 1空隙,一个1 2空隙。

不过相同的时候有个特殊情况,那就是 c 1 = c 2 = 0 c_1=c_2=0 c1=c2=0 的时候,这时候需要特判。

code:

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+5;

int T,c1,c2,c3,c4;
ll fac[maxn<<1];

ll qpow(ll a,ll b){
	ll base=a,ans=1;
	while(b){
		if(b&1){
			ans*=base;
			ans%=mod;
		}
		base*=base;
		base%=mod;
		b>>=1;
	}
	return ans;
}
ll inv(ll x){
	return qpow(x,mod-2);
}
ll C(ll a,ll b){//C_a^b
	return fac[a]*inv(fac[b])%mod*inv(fac[a-b])%mod;
}

int main(){
	fac[0]=fac[1]=1;
	for(int i=2;i<(maxn<<1);i++)
		fac[i]=fac[i-1]*i%mod;
	cin>>T;
	while(T--){
		cin>>c1>>c2>>c3>>c4;
		if(abs(c1-c2)>1){
			cout<<0<<endl;
			continue;
		}
		int a1,a2;//1 2和2 1
		if(abs(c1-c2)==1){
			a1=a2=(c1+c2+1)>>1;
			cout<<(C(a1+c3-1,c3)*C(a2+c4-1,c4)%mod)<<endl;
		}
		else {
			if(c1==0 && c2==0){
				if(c3 && c4){
					cout<<0<<endl;
					continue;
				}
				else {
					cout<<1<<endl;
					continue;
				}
			}
			a1=c1;
			a2=a1+1;
			cout<<(C(a1+c3-1,c3)*C(a2+c4-1,c4)+C(a2+c3-1,c3)*C(a1+c4-1,c4))%mod<<endl;
		}
	}
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/391495.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式 day23

链接命令 建立链接文件&#xff1a;ln 命令 命令名称&#xff1a;ln 命令所在路径&#xff1a;/bin/ln 执行权限&#xff1a;所有用户 语法&#xff1a;ln -s [原文件] [目标文件] -s 创建软链接 功能描述&#xff1a;生成链接文件 范例&#xff1…

[嵌入式系统-24]:RT-Thread -11- 内核组件编程接口 - 网络组件 - TCP/UDP Socket编程

目录 一、RT-Thread网络组件 1.1 概述 1.2 RT-Thread支持的网络协议栈 1.3 RT-Thread如何选择不同的网络协议栈 二、Socket编程 2.1 概述 2.2 UDP socket编程 2.3 TCP socket编程 2.4 TCP socket收发数据 一、RT-Thread网络组件 1.1 概述 RT-Thread 是一个开源的嵌入…

不错的PMO 2024建设规划长图

公众号"PMO前沿"是国内最大的PMO组织&#xff0c;经常各个城市举办线下线上活动&#xff0c;很多专家&#xff0c;相当赞&#xff0c;而且每天还分享不少文章&#xff08;春节都不停更&#xff0c;相当感动&#xff09;&#xff0c;建议关注。看到一个不错的PMO 组织…

win32汇编获取系统信息

.data fmt db "页尺寸&#xff1a;%d",0 db "" lpsystem SYSTEM_INFO <?> szbuf db 200 dup(0) .const szCaption db 系统信息,0 .code start: invoke GetSystemInfo,addr lpsystem …

四川古力未来科技公司抖音小店:靠谱的新电商之旅

随着互联网的飞速发展&#xff0c;电商行业日新月异&#xff0c;新兴平台如抖音小店正成为消费者新的购物天堂。在众多抖音小店中&#xff0c;四川古力未来科技公司的店铺以其独特的魅力吸引了众多消费者的目光。那么&#xff0c;四川古力未来科技公司抖音小店到底靠不靠谱呢&a…

【数据结构】17 二叉树的建立

二叉树的建立 由于树是非线性结构&#xff0c;创建一颗二叉树必须首先确定树中结点的输入顺序&#xff0c;常用方法是先序创建和层序创建。 层序创建所用的节点输入序列是按数的从上至下从左到右的顺序形成的各层的空结点输入数值0。在构造二叉树过程中需要一个队列暂时存储各…

鸿蒙开发系列教程(二十三)--List 列表操作(2)

列表样式 1、设置内容间距 在列表项之间添加间距&#xff0c;可以使用space参数&#xff0c;主轴方向 List({ space: 10 }) { … } 2、添加分隔线 分隔线用来将界面元素隔开&#xff0c;使单个元素更加容易识别。 startMargin和endMargin属性分别用于设置分隔线距离列表侧…

【测试运维】性能测试经验文档总结第3篇:VuGen详解(已分享,附代码)

本系列文章md笔记&#xff08;已分享&#xff09;主要讨论性能测试相关知识。入门阶段&#xff1a;认识性能测试分类-(负载测试、压力测试、并发测试、稳定性测试)&#xff0c;常用性能测试指标-(吞吐量、并发数、响应时间、点击数...)&#xff0c;性能测试工具选择。性能脚本&…

Java实现停车场收费系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 停车位模块2.2 车辆模块2.3 停车收费模块2.4 IC卡模块2.5 IC卡挂失模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 停车场表3.2.2 车辆表3.2.3 停车收费表3.2.4 IC 卡表3.2.5 IC 卡挂失表 四、系统实现五、核心代码…

Java面向对象案例之招待朋友Friend(二)

类主要结构图 抽象类&#xff1a;Friend&#xff08;朋友作为父类&#xff09;子类&#xff1a;Chinese&#xff08;中国国籍&#xff09;、Foreigner&#xff08;外国国籍&#xff09;主人&#xff1a;Master&#xff08;主人&#xff0c;用来款待客人&#xff09;测试类&…

ClickHouse--12-可视化工具操作

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 可视化工具操作1 tabixhttp://ui.tabix.io/ 2 DBeaverhttps://dbeaver.io/download/ 可视化工具操作 1 tabix tabix 支持通过浏览器直接连接 ClickHouse&#xff…

Kubernetes 元信息与控制器模型

一、资源元信息&#xff1a; Kubernetes 的资源对象组成&#xff1a;主要包括了 Spec、Status 和元数据。其中 Spec 部分用来描述期望的状态&#xff0c;Status 部分用来描述观测到的状态。 元数据主要包括了&#xff1a;Labels 用来识别资源的标签&#xff1b;Annotations 用…

在 Android 上部署自定义 YOLOv8 教程

在本教程中&#xff0c;我将向您展示如何在 Android 设备上使用自定义数据集部署 YOLOv8。想要了解如何在 Android 设备上使用您自己的数据集部署 YOLOv8&#xff1f;本文将展示如何操作。 Android 上的 自定义 YOLOv8 &#x1f525; ⚡️ 结果显示标题 对从 GoPro 流式传输到移…

【刷刷刷,爽!】leetcode198. 打家劫舍

题目如上&#xff01; 这是一道非常非常标准的初级动规题。属于走楼梯的进阶版。所以我们尝试把他变成走楼梯。 怎么变&#xff1f;或者说是怎么看成走楼梯。 答案是&#xff01;&#xff01;&#xff01;&#xff01; 看最后一个数。 往往会最有灵感。 比如示例1中[1,2,3,4]&a…

免费申请一个美国EDU学生邮箱

EDU邮箱的作用 例如大名鼎鼎的GitHub学生包。包含各种服务器的优惠卷&#xff0c;可以让你免费使用1-2年的服务器。免费的域名。免费的网站证书。太多了。 微软&#xff1a;免费的5T的OneDrive账户。 Google&#xff1a;G Sutie Drive无限容量 微软、苹果、AWS、都有针对学…

Sora和Pika,RunwayMl,Stable Video对比!网友:Sora真王者,其他都是弟

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

SpringCloud-Nacos集群搭建

本文详细介绍了如何在SpringCloud环境中搭建Nacos集群&#xff0c;为读者提供了一份清晰而详尽的指南。通过逐步演示每个关键步骤&#xff0c;包括安装、配置以及Nginx的负载均衡设置&#xff0c;读者能够轻松理解并操作整个搭建过程。 一、Nacos集群示意图 Nacos&#xff0…

17.3.1.6 自定义处理

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 模拟某款图像处理软件的处理&#xff0c;它只留下红色、绿色或者蓝色这样的单一颜色。 首先按照颜色划分了6个色系&#xff0c;分别…

2.16学习总结

1.邮递员送信&#xff08;dijkstra 不只是从起到到目标点&#xff0c;还要走回去&#xff09; 2.炸铁路(并查集) 3.统计方形&#xff08;数据加强版&#xff09;&#xff08;排列组合&#xff09; 4.滑雪&#xff08;记忆化&#xff09; 5.小车问题&#xff08;数学问题&#x…

二叉树前序中序后序遍历(非递归)

大家好&#xff0c;又和大家见面啦&#xff01;今天我们一起去看一下二叉树的前序中序后序的遍历&#xff0c;相信这个对大家来说是信手拈来&#xff0c;但是&#xff0c;今天我们并不是使用常见的递归方式来解题&#xff0c;我们采用迭代方式解答。我们先看第一道前序遍历 1…