【问题标题】:O(n^2) isn't fast enough in solving this. any faster approaches?O(n^2) 在解决这个问题上不够快。任何更快的方法?
【发布时间】:2012-11-06 07:27:20
【问题描述】:

我一直在尝试在 ACM Timus 上解决这个问题

http://acm.timus.ru/problem.aspx?space=1&num=1932

我的第一种方法是 O(n^2),它肯定不够快,无法通过所有测试。下面的 O(n^2) 代码在测试 10 中给出了 TL。

import java.util.*;
import java.io.*;

public class testtest
{
    public static void main(String[] args) throws IOException
    {
        BufferedReader rr = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(rr.readLine());
        String[] p = new String[n];
        for (int i = 0; i < n; i ++)
        {
            p[i] = rr.readLine();
        }
        int[] diff = new int[]{0, 0, 0, 0};
        for (int i = 0; i < n - 1; i ++)
        {
            for (int j = i + 1; j < n; j ++)
            {
                int ans  = (p[i].charAt(0) == p[j].charAt(0) ? 0 : 1) +
                           (p[i].charAt(1) == p[j].charAt(1) ? 0 : 1) +
                           (p[i].charAt(2) == p[j].charAt(2) ? 0 : 1) +
                           (p[i].charAt(3) == p[j].charAt(3) ? 0 : 1);
                diff[ans - 1] ++;
            }
        }
        System.out.print(diff[0] + " " + diff[1] + " " + diff[2] + " " + diff[3]);
    }
}

有什么想法可以让这种方法更快吗?我注意到输入中只允许有限的一组字符('0' .. '9', 'a' .. 'f'),所以我们可以创建数组(内存限制就足够了)来快速检查是否以前输入过字符。

谢谢...我不需要实际实施,只需快速的想法/想法就可以了。 编辑:感谢您的好主意。我已经尝试使用位逻辑对 O(n^2) 进行改进,但仍然超出了时间限制。帕斯卡代码如下。

program Project2;

{$APPTYPE CONSOLE}

var
  i, j, n, k, bits: integer;
  arr: array[1..65536] of integer;
  diff: array[1..4] of integer;
  a, b, c, d: char;

function g(c: char): integer; inline;
begin
  if ((c >= '0') and (c <= '9')) then
  begin
    Result := Ord(c) - 48;
  end
  else
  begin
    Result := Ord(c) - 87;
  end;
end;

begin
  Readln(n);
  for i := 1 to n do
  begin
    Read(a); Read(b); Read(c); Readln(d);
    arr[i] := g(a) * 16 * 16 * 16 + g(b) * 16 * 16 + g(c) * 16 + g(d);
    for j := 1 to i - 1 do
    begin
      bits := arr[i] xor arr[j];
      k := ((bits or (bits shr 1) or (bits shr 2) or (bits shr 3)) and $1111) mod 15;
      Inc(diff[k]);
    end;
  end;
  Write(diff[1], ' ', diff[2], ' ', diff[3], ' ', diff[4]);
{$IFNDEF ONLINE_JUDGE}
  Readln;
{$ENDIF}
end.

所以我想,我必须尝试其他更好的建议..

编辑:我已经尝试过 Daniel 的算法,它非常有前途,也许下面的代码有错误,它在测试 10 中不断得到错误的答案......有人可以看看吗?非常感谢...

import java.util.*;
import java.io.*;

public class testtest
{
    private static int g(char ch)
    {
        if ((ch >= '0') && (ch <= '9'))
        {
            return (int)ch - 48;
        }
        return (int)ch - 87;
    }

    public static void main(String[] args) throws IOException
    {
        BufferedReader rr = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(rr.readLine());
        int[] p = new int[n];
        int[] all = new int[65536];
        int[][] miss = new int[4][4096];
        int[] g12 = new int[256];
        int[] g13 = new int[256];
        int[] g14 = new int[256];
        int[] g23 = new int[256];
        int[] g24 = new int[256];
        int[] g34 = new int[256];
        int[][] gg = new int[4][16];
        int same3, same2, same1, same0, same4;
        for (int i = 0; i < n; i ++)
        {
            String s = rr.readLine();
            int x = g(s.charAt(0)) * 4096 + g(s.charAt(1)) * 256 + g(s.charAt(2)) * 16 + g(s.charAt(3));
            p[i] = x;
            all[x] ++;
            miss[0][x >> 4] ++;
            miss[1][(x & 0x000F) | ((x & 0xFF00) >> 4)] ++;
            miss[2][(x & 0x00FF) | ((x & 0xF000) >> 4)] ++;
            miss[3][x & 0x0FFF] ++;
            g12[x >> 8] ++;
            g13[((x & 0x00F0) >> 4) | ((x & 0xF000) >> 8)] ++;
            g14[(x & 0x000F) | ((x & 0xF000) >> 8)] ++;
            g23[(x & 0x0FF0) >> 4] ++;
            g24[(x & 0x000F) | ((x & 0x0F00) >> 4)] ++;
            g34[x & 0x00FF] ++;
            gg[0][x >> 12] ++;
            gg[1][(x & 0xF00) >> 8] ++;
            gg[2][(x & 0xF0) >> 4] ++;
            gg[3][x & 0xF] ++;
        }

        same4 = 0;
        for (int i = 0; i < 65536; i ++)
        {
            same4 += (all[i] - 1) * (all[i]) / 2;
        }

        same3 = 0;
        for (int i = 0; i < 4096; i ++)
        {
            same3 += (miss[0][i] - 1) * (miss[0][i]) / 2;
            same3 += (miss[1][i] - 1) * (miss[1][i]) / 2;
            same3 += (miss[2][i] - 1) * (miss[2][i]) / 2;
            same3 += (miss[3][i] - 1) * (miss[3][i]) / 2;
        }

        same2 = 0;
        for (int i = 0; i < 256; i ++)
        {
            same2 += (g12[i] - 1) * g12[i] / 2;
            same2 += (g13[i] - 1) * g13[i] / 2;
            same2 += (g14[i] - 1) * g14[i] / 2;
            same2 += (g23[i] - 1) * g23[i] / 2;
            same2 += (g24[i] - 1) * g24[i] / 2;
            same2 += (g34[i] - 1) * g34[i] / 2;
        }

        same1 = 0;
        for (int i = 0; i < 16; i ++)
        {
            same1 += (gg[0][i] - 1) * gg[0][i] / 2;
            same1 += (gg[1][i] - 1) * gg[1][i] / 2;
            same1 += (gg[2][i] - 1) * gg[2][i] / 2;
            same1 += (gg[3][i] - 1) * gg[3][i] / 2;
        }

        same3 -= 4 * same4;
        same2 -= 6 * same4 + 3 * same3;
        same1 -= 4 * same4 + 3 * same3 + 2 * same2;
        same0 = (int)((long)(n * (n - 1) / 2) - same4 - same3 - same2 - same1);
        System.out.print(same3 + " " + same2 + " " + same1 + " " + same0);
    }
}

编辑 终于拿到了 AC...感谢 Daniel 提供了如此出色的算法!

【问题讨论】:

  • +1 向我介绍了您链接的解谜网站。我一直在寻找更多的编程难题。
  • no prob :),还有其他几个在线评委。例如,www.codeforces.com,但我最喜欢 acm.timus.ru
  • 啊,是的,内存限制无法为每对可能的盗版标识符创建一个 4 Gb 的查找表...
  • @DoctorLai:检查我提供的解决方案,它也是got accepted 在重新格式化后。干杯!
  • 我看到@DoctorLai 为这个难题提供了迄今为止最快的解决方案!

标签: java string algorithm


【解决方案1】:

对于小的n,检查每一对的蛮力O(n²) 算法当然会更快,因此人们希望找到一个好的切换算法的截止点。在没有测量的情况下,我预计值在 200 到 3000 之间,因为考虑到信封背面不均匀。

通过将海盗 ID 解析为十六进制数字,将其转换为 int。将 ID 存储在

int[] pirates = new int[n];

首先,计算具有相同ID的海盗对的数量(这里可以省略这一步,因为问题陈述中没有)。

int[] allFour = new int[65536];
for(int i = 0; i < n; ++i) {
    allFour[pirate[i]] += 1;
}
int fourIdentical = 0;
for(int i = 0; i < 65536; ++i) {
    fourIdentical += allFour[i]*(allFour[i] - 1) / 2;
}

接下来,计算 ID 中包含三个相同半字节的海盗对,

int oneTwoThree(int p) {
    return p >> 4;
}
int oneTwoFour(int p) {
    return (p & 0x000F) | ((p & 0xFF00) >> 4);
}
int oneThreeFour(int p) {
    return (p & 0x00FF) | ((p & 0xF000) >> 4);
}
int twoThreeFour(int p) {
    return p & 0x0FFF;
}

int[] noFour  = new int[4096];
int[] noThree = new int[4096];
int[] noTwo   = new int[4096];
int[] noOne   = new int[4096];

for(int i = 0; i < n; ++i) {
    noFour[oneTwoThree(pirate[i])] += 1;
    noThree[oneTwoFour(pirate[i])] += 1;
    noTwo[oneThreeFour(pirate[i])] += 1;
    noOne[twoThreeFour(pirate[i])] += 1;
}

int threeIdentical = 0;
for(int i = 0; i < 4096; ++i) {
    threeIdentical += noFour[i]*(noFour[i]-1) / 2;
}
for(int i = 0; i < 4096; ++i) {
    threeIdentical += noThree[i]*(noThree[i]-1) / 2;
}
for(int i = 0; i < 4096; ++i) {
    threeIdentical += noTwo[i]*(noTwo[i]-1) / 2;
}
for(int i = 0; i < 4096; ++i) {
    threeIdentical += noOne[i]*(noOne[i]-1) / 2;
}

但是,对于每个可能选择的三个半字节,每对具有四个相同半字节的海盗都被计算4 choose 3 = 4 次,所以我们需要减去它(好吧,不是为了问题,而是原则上) :

threeIdentical -= 4*fourIdentical;

然后,计算 ID 中有两个相同半字节的海盗对:

int oneTwo(int p) {
    return p >> 8;
}
int oneThree(int p) {
    return ((p & 0x00F0) >> 4) | ((p & 0xF000) >> 8);
}
int oneFour(int p) {
    return (p & 0x000F) | ((p & 0xF000) >> 8);
}
int twoThree(int p) {
    return (p & 0x0FF0) >> 4;
}
int twoFour(int p) {
    return (p & 0x000F) | ((p & 0x0F00) >> 4);
}
int threeFour(int p) {
    return p & 0x00FF;
}

分配6个数组,每组256个ints,并在ab的地方用对应的半字节统计海盗的数量,比如

int twoIdentical = 0;
int[] firstTwo = new int[256];
for(int i = 0; i < n; ++i) {
    firstTwo[oneTwo(pirate[i])] += 1;
}
for(int i = 0; i < 256; ++i) {
    twoIdentical += firstTwo[i]*(firstTwo[i] - 1) / 2;
}
// analogous for the other five possible choices of two nibbles

但是,这里有四个相同半字节的对已经计算了4 choose 2 = 6 次,而具有三个相同半字节的对已经计算了3 choose 2 = 3 次,所以我们需要减去

twoIdentical -= 6*fourIdentical + 3*threeIdentical;

接下来,具有一个相同半字节的对数。我相信您可以猜到需要哪些四个函数和数组。然后我们将计算四个相同半字节的对 4 choose 1 = 4 次,三个相同半字节的对 3 choose 1 = 3 次,以及两个相同半字节的对 2 choose 1 = 2 次,所以

oneIdentical -= 4*fourIdentical + 3*threeIdentical + 2*twoIdentical;

最后,没有相同半字节的对数是

int noneIdentical = (int)((long)n*(n-1) / 2) - oneIdentical - twoIdentical - threeIdentical - fourIdentical;

(转换为long 以避免n*(n-1) 溢出)。

【讨论】:

  • 谢谢丹尼尔,我已经尝试过你的方法,你的算法很有前途,但是我在这里错过了一些东西,因为它对测试 10 给出了错误的答案,我不知道错误在哪里...
  • 您认为将所有数组长度设为 65536 并屏蔽掉不需要的半字节而不是移动位会更有效吗?
  • 我怀疑区别,因为在这种情况下,我们应该循环 65536 个元素来对计数器求和。
  • 嗨丹尼尔,我已经根据你的算法编辑了我的帖子......你能发现任何错误吗?因为它在测试 10 上总是得到错误的答案...谢谢。
  • @DoctorLai 在same0 = (int)((long)(n * (n - 1) / 2) - same4 - same3 - same2 - same1);,你投得太晚了。使用这些括号,n*(n-1) 被计算为int,并且可能会溢出。我有 (int)((long)n * (n-1) / 2)long 的演员发生在乘法之前。
【解决方案2】:

这可能是比较标识符对的一种快速方法。它假定两个标识符都作为十六进制数字而不是字符串被读入。

int bits = p[i] ^ p[j];
int ans = ((bits | bits >> 1 | bits >> 2 | bits >> 3) & 0x1111) % 15;
diff[ans - 1]++;

【讨论】:

  • 你确定这是正确的吗?因为我试过你的方法,但结果不正确。
  • @DoctorLai 我只在0xdead0xbeef0xf00d 上试过。它返回第一个和其他两个中的任何一个之间的 3 个差异以及其他两个之间的 4 个差异。你有一个你认为它返回错误答案的例子吗?
  • 谢谢尼尔,对不起,你的方法是正确的...我之前误解了...但是,仍然是TL,检查我的最新编辑。
  • @DoctorLai 实际上,在最坏的情况下,循环将执行近 2^31 次。如果循环为空,则 2GHz 处理器可以在 1 秒内完成此操作,这不会为实际代码留下太多时间! (我之前没有注意到时间限制。)
【解决方案3】:

这个怎么样:

创建一个 4 维整数数组,每个维度的元素宽度为 16,所有元素都初始化为 0。所以,这将是16*16*16*16*4 = 262144 字节,大约是262KB。现在,在读取每个标识符时将它们索引(插入)到该结构中。索引时,在每个维度上增加 counter...

当您对该结构进行索引时,您可以逐步计算所需的答案。

例如,假设我正在索引标识符 dead。在第一个维度上,选择d对应的位置,假设这个位置的计数器是n1,也就是说到目前为止已经有n1标识符带有d 在位置 1。现在取 e 并将其插入到第二维上的正确位置...并说该位置的计数器是n2,那么我确定有n1 - n2 标识符与当前插入的标识符相比在 3 个位置不同...

编辑:我开始怀疑自己了。如果两个标识符在第一个字符中不同但在第二个字符中相等怎么办?这种方法将无法接受我的想法。需要更多思考...

编辑:暂停“智能解决方案”后,我尝试改进基本的O(n^2) 算法,如下所示。

每个标识符都有 4 个十六进制数字,因此可以打包成一个整数(不过一个 2 字节的字就足够了)。所以,这样的整数看起来像:

[ ][ ][ ][ ] * [ ][ ][ ][ ] * [ ][ ][ ][ ] * [ ][ ][ ][ ]

其中每个[ ] 代表一个位,每个[ ][ ][ ][ ] 组(半字节)对应于标识符中的一个十六进制数字。现在我们需要一种方法来快速比较这两个标识符。给定两个标识符ab,首先我们计算c = a ^ b,这将为我们提供两个位模式的XOR(差异,有点)。现在我们将这个位模式c 与以下常量模式进行比较:

u = [1][1][1][1] * [0][0][0][0] * [0][0][0][0] * [0][0][0][0]

v = [0][0][0][0] * [1][1][1][1] * [0][0][0][0] * [0][0][0][0]

w = [0][0][0][0] * [0][0][0][0] * [1][1][1][1] * [0][0][0][0]

x = [0][0][0][0] * [0][0][0][0] * [0][0][0][0] * [1][1][1][1]

对比如下:

diffs = 0

if (c & u)
  diffs++
if (c & v)
  diffs++
if (c & w)
  diffs++
if (c & x)
  diffs++

在此操作结束时,diffs 将包含 ab 不同的字符数。请注意,无需将标识符打包成整数即可实现相同的方法(即将每个十六进制数字保留在其自己的整数/字符类型上),但我认为这种打包节省的内存可能意味着什么。

恕我直言,这只是对基本@9​​87654343@ 算法的微小改进。如果接受的答案是这种 诡计 而不是基于更好的算法/数据结构的答案,我会非常失望。急切地等待有人就这样的答案提供一些建议...... :-)

【讨论】:

  • 正是...我尝试过类似的方法..但是如果您列出一些示例,它会变得复杂..也许 O(n^2) 是必要的。
  • 我不认为这些问题可以通过喝一杯茶来解决......(除非你是专业人士)。可能有成千上万种方法可以弄错。但这就是它的美丽!感谢分享这只野兽,现在我上瘾了;-)
  • 很高兴你喜欢它......是的......我喜欢在 ACM timus 上解决问题......这很有趣。
  • 我已经附加了答案,可能对O(n^2) 算法进行了改进。但是我怀疑这个问题有一个比O(n^2)更好的解决方案。
  • 谢谢...尝试改进 O(n^2)(请参阅我的编辑),但是,仍然是 TL。
【解决方案4】:

这可以通过单次遍历 id 列表来解决,因此需要 O(n) 时间,但您仍然需要小心,以便实现及时运行。

考虑查找相等字符串对的问题。这可以通过跟踪以前在地图中找到的每个字符串的计数来完成:

long pairs = 0;
Set<String, Long> map = new HashMap<String, Long>();
for(String id:ids) {
    if(map.containsKey(id)) {
        pairs += map.get(id);
        map.put(id, map.get(id) + 1);
    } else {
        map.put(id, 1);
    }
}

现在查找一个字符不同的对。您可以存储不包括每个字符的字符串。所以对于“abcd”,存储“.bcd”、“a.cd”、“ab.d”和“abc”。在地图中。对于每个 id,您可以为每个缺失的字符位置添加每个字符串的计数。这也将计算相等的字符串,因此减去相等字符串的计数。

使用Inclusion/Exclusion principle 泛化到更多缺失的字符。例如,s 中具有 2 个不同字符的字符串的计数为:

sum of all character positions `x` and `y`:
    the number of strings equal to `s` excluding characters `x` and `y`
    - the number of strings equal to `s` excluding character `x`
    - the number of strings equal to `s` excluding character `y`
    + the number of strings equal to `s`

出于效率原因,您不想进行字符串操作,因此实际实现应该将字符串转换为整数。此外,您可以使用长数组代替 Map(其中索引表示字符串,值是匹配字符串的计数)。

【讨论】:

    【解决方案5】:

    您有n!/(n-2)!2! = O(n2) 对(n - 输入数),无论您做什么,都需要检查它们,因为这是要求。

    您可以通过将整个输入插入DAWG 并稍后遍历它来优化时间。对于 DAWG 中的任意 2 条路径,相似性定义为由两条路径中存在的边组成的集合。构造此集合并从路径长度中减去其大小以找到差异距离。

    【讨论】:

    • 你确定吗? O(n^2) 似乎太慢了.. 因为在 O(n^2) 循环中仅检查 4 个字符的差异仍然需要太多时间。
    • @DoctorLai 您输入的大小是 n 个字符串 --> O(n^2) 不同的对。不管你做什么,你都需要检查所有的对。
    • 您检查过其他建议吗? O(n^2) 无论如何都会给出 TL。
    • 它仍然是 O(n^2),但常数可以更小 - 例如,如果你使用我的建议
    【解决方案6】:

    您可以只计算匹配然后计算对,而不是计算对吗?以 d 开头的 IE 5 代码实际上是 5! = 10 对。

    这将允许您使用一种结构,该结构可以在插入时而不是迭代地计算位置匹配。

    编辑:

    当时我在想也许有一个带有根节点和 4 个嵌套级别的树结构。每个级别代表代码中的一个位置。 IE 根目录 -> d -> e -> a -> f。当您插入每个代码时,如果该字符在该级别不存在,则插入它,如果它确实符合它。在每次代码插入结束时,您将知道该代码有多少匹配项,然后将其添加到 diff 中的相应存储桶中。然后通过取 n! 转换为对,而不仅仅是匹配项!每个桶的。

    【讨论】:

    • 有趣的想法...你能提供更多细节吗?
    • 扩展了我的帖子。让我知道这对我以外的其他人是否有意义。
    【解决方案7】:

    我们可以将私有数映射到一个 3 维矩阵 P[x][y][z] 吗?

    • x 是私有数(2 ≤ n ≤ 65536)。
    • y 是 0 到 15 (0 - 9, a - f) 之间的标识符。
    • z 将是 1,包含标识符 (0 - f) 的选项。

    例如:

    死了
    牛肉
    f00d

    相应的 p[x][y] 将是

    0 1 2 3 4 5 6 7 8 9 a b c d e f

    0 0 0 0 0 0 0 0 0 0 1 0 0 1 1 0
    0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 1
    1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1

    z 维度将是

    0 0 0 0 0 0 0 0 0 2 0 0 9 4 0 (2 -> 0010, 9 -> 1001, 4 -> 0100)
    0 0 0 0 0 0 0 0 0 0 8 0 0 6 1 (6 -> 0110, 8 -> 1000, 1 -> 0001)
    6 0 0 0 0 0 0 0 0 0 0 0 1 0 8

    我们逐列检查p[x][y],如果2个海盗有相同的标识符,那么我们在他们的位置之间做一个“&”位运算并保存结果(可能需要另一个矩阵来决定有多少乘以当前列中新匹配之前匹配的这 2 个海盗,O(1))。

    遍历这个矩阵一次可能就足够了 O(n)。

    尚未通过代码证明,这是个好主意。希望能帮助到你。

    感谢这个谜题。

    【讨论】:

      【解决方案8】:

      此答案是对@fgb / @Daniel 提供的答案的详细说明。

      让我们用H1H2H3H4(四个十六进制数字)表示每个标识符

      1. 我们有 四个 数组,每个数组的长度为 16*16*16 (4096)。这四个数组将用于跟踪在 3 个位置相等的标识符。

      2. 对于读取的每个标识符,计算值(整数)H2H3H4H1H3H4H1H2H4H1H2H3(请注意,这些值的范围是 0 - 4095;这就是我们选择数组的原因大小为 4096)。现在,使用这些值中的每一个来索引步骤(1)中提到的四个数组,并将相应的位置加一。

      3. 我们还有另外 六个 数组,每个数组的长度为 16*16 (256)。这五个数组将用于跟踪在 2 个位置相等的标识符。

      4. 对于读取的每个标识符,计算值(整数)H3H4H2H4H1H4H1H3H1H2H2H3。现在,使用这些值中的每一个来索引步骤(3)中提到的六个数组,并将相应的位置加一。每个数组位置有效地计算在 2 个位置相等的标识符的数量(但请注意,此计数也将包括在 3 个位置相等的标识符的数量)。

      5. 我们还有另外 四个 数组,每个数组的宽度为 16。这四个数组将用于跟踪在 1 个位置相等的标识符。

      6. 对于读取的每个标识符,计算值(整数)H4H3H2H1。现在,使用这些值中的每一个来索引步骤(5)中提到的四个数组,并将相应的位置加一。每个数组位置有效地计算在 1 个位置相等的标识符的数量(同样,这个计数将包括在 3、2 个位置相等的标识符的数量)。

      请注意,所有这些都可以通过输入一次完成。但是,这还不足以计算最终答案,我们必须在步骤 1 - 6 的同时逐步计算最终答案。我们可以这样做:

      diff1 = 0
      diff2 = 0
      diff3 = 0
      diff4 = 0
      
      • 在步骤(2)中,当我们增加某个数组位置时,如果结果值为u (u &gt; 1),则为diff1 = diff1 + (u - 1)。这意味着,使用已经找到的 (u - 1) 标识符,我们可以创建 (u - 1) 新对(通过将每个标识符与当前标识符配对)。

      • 在步骤(4)中,当我们增加某个数组位置时,如果结果值为v (v - u &gt;= 1),则为diff2 = diff2 + (v - u)。这意味着,我们知道在上一步中已经考虑了u 数量的标识符,它们不应该被重新计算。因此,(v - u) 2 个位置给出与此标识符相等/不同的其他标识符。使用这些标识符,我们可以创建 (v - u) 新对(通过将每个标识符与当前标识符配对)。

      • 在步骤(6)中,当我们增加某个数组位置时,如果结果值为w (w - u &gt;= 1),则为diff3 = diff3 + (w - u)。解释和上面的很相似。

      再次注意,所有这些步骤都可以在整个输入的单次通过期间完成。

      现在,接下来是如何计算diff4。这可以在步骤 (2) 中完成,当我们增加某个数组位置时,如果新值为 1,则 diff4 = diff4 + 1 如果新值为 2,则 diff4 = diff4 - 1。这会跟踪到目前为止尚未找到匹配标识符的新鲜标识符。

      更新:上述方法效果很好,但我建议的计算diff4 的方法不起作用。基本上,diff4 不是关于唯一标识符,而是我们可以通过组合在所有 4 个位置上与 彼此 不同的标识符来形成多少对。

      在 C 中的以下实现中采用了不同的方法(用于 diff4):

      #include <stdio.h>
      #include <stdlib.h>
      
      // forward declarations
      __inline int update_same3 (int x1, int x2, int x3, int* map, int* diff1, int* overlaps);
      __inline int update_same2 (int x1, int x2, int u, int* map, int* diff2, int* overlaps);
      __inline void update_same1 (int x1, int v, int* map, int* diff3, int* overlaps);
      
      int main () {
        // number of input identifiers
        int n = 0;
      
        // each identifier represented as four hexa-decimal digits
        int h1, h2, h3, h4; 
      
        // final answer
        int diff1 = 0;
        int diff2 = 0;
        int diff3 = 0;
        int diff4 = 0;
      
        int i, u1, u2, u3, u4, v1, v2, v3, v4, v5, v6, overlaps;
      
        // maps for keeping track of the identifiers
        int* maps [14];
        maps[0] = (int*) calloc (4096, sizeof(int)),  // h1h2h3
        maps[1] = (int*) calloc (4096, sizeof(int)),  // h2h2h4
        maps[2] = (int*) calloc (4096, sizeof(int)),  // h1h3h4
        maps[3] = (int*) calloc (4096, sizeof(int)),  // h2h3h4
        maps[4] = (int*) calloc (256, sizeof(int)),  // h1h2
        maps[5] = (int*) calloc (256, sizeof(int)),  // h1h3
        maps[6] = (int*) calloc (256, sizeof(int)),  // h1h4
        maps[7] = (int*) calloc (256, sizeof(int)),  // h2h3
        maps[8] = (int*) calloc (256, sizeof(int)),  // h2h4
        maps[9] = (int*) calloc (256, sizeof(int)),  // h3h4
        maps[10] = (int*) calloc (16, sizeof(int)),  // h1
        maps[11] = (int*) calloc (16, sizeof(int)),  // h2
        maps[12] = (int*) calloc (16, sizeof(int)),  // h3
        maps[13] = (int*) calloc (16, sizeof(int)),  // h4
      
        // main loop
        fscanf (stdin, "%d\n", &n);
        for (i = 0; i < n; i++) {
          // scan identifier 
          fscanf (stdin, "%1x%1x%1x%1x", &h1, &h2, &h3, &h4);
      
          // keeps track of the number of identifiers that the current
          // identifier overlaps with, required for calculating diff4
          overlaps = 0;
      
          u1 = update_same3 (h1, h2, h3, maps[0], &diff1, &overlaps); //h1h2h3
          u2 = update_same3 (h1, h2, h4, maps[1], &diff1, &overlaps); //h1h2h4
          u3 = update_same3 (h1, h3, h4, maps[2], &diff1, &overlaps); //h1h3h4
          u4 = update_same3 (h2, h3, h4, maps[3], &diff1, &overlaps); //h2h3h4
      
          v1 = update_same2 (h1, h2, u1 + u2, maps[4], &diff2, &overlaps); //h1h2
          v2 = update_same2 (h1, h3, u1 + u3, maps[5], &diff2, &overlaps); //h1h3
          v3 = update_same2 (h1, h4, u2 + u3, maps[6], &diff2, &overlaps); //h1h4
          v4 = update_same2 (h2, h3, u1 + u4, maps[7], &diff2, &overlaps); //h2h3
          v5 = update_same2 (h2, h4, u2 + u4, maps[8], &diff2, &overlaps); //h2h4
          v6 = update_same2 (h3, h4, u3 + u4, maps[9], &diff2, &overlaps); //h3h4
      
          update_same1 (h1, (v1 + v2 + v3) - (u1 + u2 + u3), maps[10], &diff3, &overlaps); //h1
          update_same1 (h2, (v1 + v4 + v5) - (u1 + u2 + u4), maps[11], &diff3, &overlaps); //h2
          update_same1 (h3, (v2 + v4 + v6) - (u1 + u3 + u4), maps[12], &diff3, &overlaps); //h3
          update_same1 (h4, (v3 + v5 + v6) - (u2 + u3 + u4), maps[13], &diff3, &overlaps); //h4
      
          // if the current identifier overlapped with n existing identifiers
          // then it did not overlap with (i - n) identifiers
          if (i - overlaps > 0)
            diff4 += (i - overlaps); 
        }
      
        // print results
        printf ("%d %d %d %d\n", diff1, diff2, diff3, diff4);
      }
      
      // identify overlaps at 3 positions
      __inline int update_same3 (int x1, int x2, int x3, int* map, int* diff1, int* overlaps) {
        int idx = (x1 << 8 | x2 << 4 | x3);
        int u = (map[idx])++;
        if (u > 0) {
          (*diff1) += u;
          (*overlaps) += u;
        }
        return u;
      }
      
      // identify overlaps at 2 positions
      __inline int update_same2 (int x1, int x2, int u, int* map, int* diff2, int* overlaps) {
        int idx = (x1 << 4 | x2);
        int v = (map[idx])++;
        if (v - u > 0) {
          (*diff2) += (v - u);
          (*overlaps) += (v - u);
        }
        return v;
      }
      
      // identify overlaps at 1 position
      __inline void update_same1 (int x1, int v, int* map, int* diff3, int* overlaps) {
        int w = (map[x1])++;
        if (w - v > 0) {
          (*diff3) += (w - v);
          (*overlaps) += (w - v);
        }
      }
      

      我用自己的一些示例输入对此进行了测试,它似乎有效。如果 OP 可以对此进行测试并查看它是否有效,那就太好了。它可能需要更多的工作。

      更新:好的,it works!必须重新格式化代码以使其通过那里的编译器。我还更新了此处列出的代码以反映更改。

      【讨论】:

      • 很好,但你忘了H2H3
      • 已修复,谢谢!。我无法想象在一个小时左右的时间内解决 + 编码这种问题......!!! (可能需要大量练习)
      • 它在“2012年乌拉尔公开赛FU锦标赛和2012年东欧公开杯大奖赛第2赛区”中被列为第二个问题......,......有10个问题......所以它是被认为是第二个最简单的问题……参加 ACM 的那些人是天才。他们绝对是专业人士..
      • 我相信这是这个问题的最佳解决方案。我已经根据这个算法编辑了我的帖子,但是有一个错误我无法发现 atm,这在测试 10 中给出了错误的答案。
      • 您的代码更接近 Daniel 的答案(顺便说一句,您不需要 same4 循环,问题表明所有标识符都彼此不同)。另一方面,我的阐述主要基于 fgb 的回答,请注意,使用这种方法,您不需要任何额外的循环,只需要一个循环来读取输入(所有其他操作都是可以在该循环中执行的插入/计算每个标识符都读取)。
      【解决方案9】:

      您可以尝试以下方法吗:

      当您处理每个条目时,将其添加到位置一有特定字母的名称列表中。列表中的任何条目都已添加到“4 个匹配候选者”列表中。

      然后将其添加到具有特定第二个字母的名称列表中。此列表中已有的“4 个匹配候选者”中的任何条目仍保留在“4 个匹配候选者”中,“4 个匹配候选者”中的所有其他条目与该列表中的所有条目一起移动到“3 个匹配候选者”列表中.

      然后将其添加到具有特定第三个字母的名称列表中。此列表中已有的“3 个匹配候选者”中的任何条目仍保留在“3 个匹配候选者”中,“3 个匹配候选者”中的所有其他条目与该列表中已有的所有条目一起移动到“2 个匹配候选者”列表中列表。此列表中“4 个匹配候选者”中的任何条目都保留在“4 个匹配候选者”中,“4 个匹配候选者”中的所有其他条目都移动到“3 个匹配候选者”列表中。

      然后将其添加到具有特定第四个字母的名称列表中。此列表中的任何条目都添加到 1 个匹配候选者的计数中,“2 个匹配候选者”列表中的任何条目都添加到 2 个匹配候选者的计数中,“3 个匹配候选者”列表中的任何条目都添加到3 个匹配候选者的计数,并且“4 个匹配候选者”列表中的任何条目都将添加到 4 个匹配候选者的计数中。

      这应该意味着(希望)您只处理列表一次,而不是检查每个其他条目,您只需要检查在相同位置具有相同字母的子集。

      或者我需要睡觉...

      编辑:

      所以我昨晚误解了目标。上面的过程计算了相似之处而不是差异,但是我们可以使用类似的方法来计算差异。基本上为1个差异词、2个差异词、3个差异词和4个差异词保留4个列表。将到目前为止我们看到的所有单词添加到 4 个不同单词的列表中。然后从包含所有现有名称的列表开始,然后查找将当前单词的第一个字母放在首位的单词列表。将列表中所有在 2 个差异列表中的单词移动到 1 个差异列表,3 个差异列表到 2 个差异列表,4 个差异列表到 3 个差异列表。

      处理完所有列表后,您可以将每个列表中的 count 添加到全局计数中。

      我认为这应该可行,但我稍后会绑定它。 ..

      【讨论】:

        猜你喜欢
        • 2021-12-22
        • 2012-12-18
        • 2020-10-29
        • 2014-11-04
        • 1970-01-01
        • 2023-03-11
        • 2021-11-24
        • 2020-08-31
        • 2022-11-29
        相关资源
        最近更新 更多