CEOI 2019 立方填词 题解

题目传送门

题目大意: 这个没图很难说啊qwq

题解

做法真是太秀了%%%

这题首先方向很重要,不能去想枚举某条边或某个面,因为边很多,太复杂,而面又难以合并,因为要考虑 4 4 个角。所以,这题要枚举角。

显然长度不同的词语是不能出现在同一个立方体里面的,那么就先按长度分类然后排序去重。

一个词语只有首尾的两个字母是有用的,不妨开一个数组记录一下, e [ a ] [ b ] e[a][b] 表示以 a a 作为首以 b b 作为尾的单词数(注意这里的 a , b a,b 是变量,下面同理)。然后大力枚举每个角的字母,然后利用 e e 统计答案,就可以得到一个 O ( 6 1 8 ) O(61^8) 的做法。

考虑优化这个枚举。

先上图(图丑莫喷,能看就好):
在这里插入图片描述
我们给每个角编个号,像上图那样。

假如从 1 1 号点出发,按广度优先搜索的顺序,我们可以将图分成四层:
第一层:     1 ~~~1
第二层: 2   3   4 2~3~4
第三层: 5   6   7 5~6~7
第四层:     8 ~~~8

我们惊奇地发现,第一层和第三层的四个点,他们之间是没有直接的边相连的!

那么又有这样一个性质:对于三个不相连的点,肯定存在一个点与他们三个都相连。

扫描二维码关注公众号,回复: 9578645 查看本文章

就像这样(其中 A , B , C A,B,C 三个点之间没有连边):
在这里插入图片描述

我们不妨设现在有三个不相连的点 A , B , C A,B,C 和一个连接他们三个的点 D D (参考上图理解),设 f [ a ] [ b ] [ c ] f[a][b][c] 1表示 往点 A A 上放字母 a a ,往点 B B 上放字母 b b ,往点 C C 上放字母 c c A , B , C , D A,B,C,D 四点之间的三条边有多少种单词放法

可能有点奇怪,往下看可能更好理解。

这样的话,我们大力枚举在 A , B , C , D A,B,C,D 上放的字母,然后利用 e e 数组就可以在 O ( 6 1 4 ) O(61^4) 的时间内求出 f f 数组。

再看回上面的问题,此时第一层和第三层一共有 4 4 个互不相连的点,我们不妨大力枚举这四个点上的字母,设他们的字母分别为 a , b , c , d a,b,c,d ,那么产生的贡献就是: p = f [ a ] [ b ] [ c ] × f [ a ] [ b ] [ d ] × f [ a ] [ c ] [ d ] × f [ b ] [ c ] [ d ] p=f[a][b][c] \times f[a][b][d] \times f[a][c][d] \times f[b][c][d]

枚举这四个字母的时间复杂度是 O ( 6 1 4 ) O(61^4) 的,那么总的时间复杂度就是 O ( 6 1 4 ) O(61^4)

但是他还卡常。

因为我们的复杂度是满的,所以跑的相当的慢。

考虑到 f [ a ] [ b ] [ c ] , f [ a ] [ c ] [ b ] , . . . , f [ c ] [ b ] [ a ] f[a][b][c],f[a][c][b],...,f[c][b][a] 其实都是一样的,那么不妨设 a b c a\leq b \leq c ,然后只求出 f [ a ] [ b ] [ c ] f[a][b][c] ,那么就可以节约 5 6 × 6 1 4 \frac 5 6 \times 61^4 的时间。

既然 f f 的参数都递增了,不妨求解的时候让枚举的 a , b , c , d a,b,c,d 也递增,因为 ( b , a , c , d ) , ( b , c , d , a ) (b,a,c,d),(b,c,d,a) 这样的贡献与 ( a , b , c , d ) (a,b,c,d) 的贡献是相同的。

但是要注意,当 a = b a=b 时, ( a , b , c , d ) (a,b,c,d) ( b , a , c , d ) (b,a,c,d) 这两个是相同的方案,不能重复计算,所以对于一组递增的 ( a , b , c , d ) (a,b,c,d) ,他产生的贡献是 p p 乘以 a , b , c , d a,b,c,d 不相同排列数(这个有点数论基础都知道怎么做吧)。

剩下不明白的结合代码理解吧:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 62
#define ll long long
#define mod 998244353

int n;
vector<string>s[11];
string ss;
int ch(char x)//将字母转化为数字来存
{
	if(x>='0'&&x<='9')return x-'0';//0~9 : 0~9
	if(x>='a'&&x<='z')return x-'a'+10;//a~z : 10~35
	if(x>='A'&&x<='Z')return x-'A'+36;//A~Z : 36~61
}
ll e[maxn][maxn],f[maxn][maxn][maxn];//数组意义如上所述
ll times[maxn],ans=0;

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		cin>>ss;
		s[ss.length()].push_back(ss);
		//因为不在意阅读顺序,所以这个串反过来也能用,也要记录下来
		for(int i=0;i<ss.length()/2;i++)
		swap(ss[i],ss[ss.length()-i-1]);
		s[ss.length()].push_back(ss);
	}
	for(int i=3;i<=10;i++)
	if(s[i].size()>0)
	{
		memset(e,0,sizeof(e));//记得每次要清空
		memset(f,0,sizeof(f));
		sort(s[i].begin(),s[i].end());//排序
		for(int j=0;j<s[i].size();j++)//去重+处理e数组
		if(j==0||s[i][j]!=s[i][j-1])e[ch(s[i][j][0])][ch(s[i][j][i-1])]++;
		
		for(int a=0;a<maxn;a++)//处理f数组
		for(int b=0;b<maxn;b++)if(e[a][b]>0)
		for(int c=b;c<maxn;c++)if(e[a][c]>0)
		for(int d=c;d<maxn;d++)if(e[a][d]>0)
		f[b][c][d]=(f[b][c][d]+e[a][b]*e[a][c]%mod*e[a][d]%mod)%mod;
		
		ll p=24;
		for(int a=0;a<maxn;a++)
		{
			times[a]++;p/=times[a];//用来解决不相同排列数
			for(int b=a;b<maxn;b++)
			{
				times[b]++;p/=times[b];
				for(int c=b;c<maxn;c++)
				{
					times[c]++;p/=times[c];
					for(int d=c;d<maxn;d++)
					{
						times[d]++;p/=times[d];
						
						ans=(ans+p*f[a][b][c]%mod*f[a][b][d]%mod*f[a][c][d]%mod*f[b][c][d]%mod)%mod;
						
						p*=times[d];times[d]--;
					}
					p*=times[c];times[c]--;
				}
				p*=times[b];times[b]--;
			}
			p*=times[a];times[a]--;
		}
	}
	printf("%lld",ans);
}

  1. 还是要提醒,这里的 a , b , c a,b,c 是变量而不是 a,b,c 这三个字母 ↩︎

发布了234 篇原创文章 · 获赞 100 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/102749153