传送门

题意:

给出一个有n个字符串的匹配串,和m个有k个字符串的文本串,将匹配串进行全排列和文本串进行匹配,找到能全匹配成功的最小的逆序对数

n<=15,m<=10,k<=500000

Solution:

通过哈希+map把字符串变成数字

那么问题就转化成了把一个1到n的序列的全排列和一些序列进行匹配,找到能全匹配成功的最小的逆序对数

一个显然的状压dp状态是能想到的: f[i][j] f [ i ] [ j ] 表示前i位已经匹配了状态为j的数,最小的逆序对数,转移可以通过预处理达到O(n),但是这样一次的复杂度是 O(k2nn) O ( k ∗ 2 n ∗ n ) 的,显然复杂度爆炸,但是转移好像不能优化的样子….

所以我们需要换个状态!

我们发现k太大了,但是我们需要的只有n个数,也就是说原状态会有很多没有用的转移

所以我们尝试让状态与n相关

f[i][j] f [ i ] [ j ] 表示产生了i个逆序对,已经匹配了状态为j的数至少需要到文本串的第几位

然后我们再预处理一下辅助数组,就可以实现O(n)转移了

这样的话一次的复杂度是 O(n2n2n) O ( n 2 ∗ n ∗ 2 n ) ,是非常优秀的

代码:

#include<cstdio>
#include<iostream>
#include<map>
#include<cstring>
using namespace std;
const int bas=211;
const int mod=1315326179;
char s[11];
char c[500010][11];
int n,m,k,ha,len;
int f[150][1<<15];
int num[20];
int go[500010][20];//i项以后最早和原序列第j项匹配的位置 
map<int,int> vis;
int ans=0,pos;
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        scanf("%s",s+1);
        len=strlen(s+1);
        ha=0;
        for (int j=1;j<=len;j++) ha=(1ll*ha*bas+s[j])%mod;
        vis[ha]=i;
    }
    scanf("%d",&k);
    for (int t=1;t<=k;t++)
    {
        scanf("%d",&m);
        for (int i=1;i<=n;i++) go[m+1][i]=1e9;
        for (int i=1;i<=m;i++) scanf("%s",c[i]+1);
        for (int i=m;i>=1;i--)
        {
            for (int j=1;j<=n;j++) go[i][j]=go[i+1][j];
            len=strlen(c[i]+1);
            ha=0;
            for (int j=1;j<=len;j++) ha=(1ll*ha*bas+c[i][j])%mod;
            int nw=vis[ha];
            if (nw) go[i][nw]=i;
        }
        bool pan=0;
        for (int i=1;i<=n;i++) {go[0][i]=go[1][i];if (go[0][i]==1e9) {pan=1;break;}}
        if (pan) continue;
        memset(f,63,sizeof(f));
        f[0][0]=0;
        for (int i=0;i<=n*(n-1)/2;i++)
        {
            if (i&&f[i-1][(1<<n)-1]<1e9) break;
            for (int j=0;j<(1<<n);j++)
                if (f[i][j]<1e9)
                {
                    if (j==((1<<n)-1)) {
  if (ans<n*(n-1)/2-i+1) pos=t,ans=n*(n-1)/2-i+1;break;}
                    for (int l=1;l<=n;l++) num[l]=num[l-1]+((j>>(l-1))&1);
                    for (int p=1;p<=n;p++)
                        if (!(1&(j>>(p-1))))
                            f[i+num[n]-num[p]][j|(1<<p-1)]=min(f[i+num[n]-num[p]][j|(1<<p-1)],go[f[i][j]][p]);
                }
        }
    }
    if (ans==0) printf("Brand new problem!");
    else {
  printf("%d\n",pos);printf("[:");for (int i=1;i<=ans;i++) printf("|");printf(":]");}
}