P4795 [BalticOI 2018] 基因工程 题解

发布时间 2023-08-04 19:50:45作者: TimelessWelkin

题目传送门:Click

蒟蒻看见这道题,想了足足一个小时,过后顿有所悟,故作此篇。

首先,看到题目,光是数据就已经达到了 \(\operatorname{O}(nm)\) 的级别,再看一看数据范围:\(3 \leq n,m \leq 4,100\)。显然是一道时间复杂度为 \(\operatorname{O}(n,m)\) 级别的题目。

本蒟蒻首先观察了样例一,统计了每一列每种字符的出现次数:

4 3 1
ACC
CCA
ACA
AAA

第一列有三个 A 和一个 C。第二列有三个 C 与一个 A。第三列有三个 A 与一个 C

接着统计了一下答案即模式串,发现其第一个字符 A 出现 3 次,与其不同的只有一个字符串;第二个字符 C 出现 3 次,与其不同的只有一个字符串;第三个字符 A 出现 4 次,与其不同的只有一个字符串。

将所有与该串不同的字符串个数加起来,发现 \(1+1+1\) 刚好为 \(k \cdot (n-1)\),即每个非模式串与模式串不同的字符数乘上触模式串以外的字符串个数。

于是乎,第一份代码产生了:前往剪贴板

(喜提 \(79 pts\)

为什么错了?(数据这么水,居然有 \(79 pts\)?)

来模拟一下样例二……发现输出 \(1\)!为什么?

稍微想一下,发现因为第 \(2\) 个字符串和第 \(1\) 个字符串有两个字符重复,在统计时,这两个字符会被认为是 \(2,3\) 两串与 \(1\) 相通的部分。总之,就是 \(2\) 号字符串与 \(3\) 号字符串,因为与 \(1\) 号字符串相同的字符贡献都为 \(1\) ,导致无法区分是 \(2\) 号字符串与 \(1\) 号字符串有两个字符相同,还是分别与 \(1\) 号有一个字符相同。

怎么解决?很简单,只要让每个字符串的字符贡献不同,即取一个随机数,就可以区分这些情况。举个栗子,\(2\) 号字符串贡献为 \(1\)\(3\) 号字符串贡献为 \(2\),那么 \(2\) 号字符串的两个字符与 \(1\) 号字符串相同会有 \(2\) 的贡献;而一个字符与 \(2\) 号相同,另一个与 \(3\) 相同,就会有 \(3\) 的贡献。这样,就把几种情况区分开来了。

改进后的代码

#include <bits/stdc++.h>
using namespace std;

const int MaxN=4105;
char s[MaxN][MaxN];
mt19937 rnd(998244353);
int n,m,k,opt[128];
typedef long long ll;
ll cnt[MaxN][4],w[MaxN],sum;

int main() {
	opt['A']=0,opt['G']=1,opt['T']=2,opt['C']=3;
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++) {
		scanf("%s",s[i]+1),w[i]=rnd();
		for(int j=1;j<=m;j++) cnt[j][opt[s[i][j]]]+=w[i];
		sum+=w[i];
	}
	for(int i=1;i<=n;i++) {
		ll res=0;
		for(int j=1;j<=m;j++)
			for(int k=0;k<4;k++) {
				if(opt[s[i][j]]==k) continue;
				res+=cnt[j][k];
			}
		if(res==(sum-w[i])*k) {
			printf("%d\n",i);
			return 0;
		}
	}
	return 0;
}

这里使用了 C++11 里的随机数 mt19937,借鉴于神犇的题解

这样,有成功地水了一篇题解。