一、题目
二、解法
没有什么好的做法,那就考虑网络流把,首先可以单独处理回文串的情况(贡献为
1
1
1),然后把最少有多少个单词,满足翻转后依然是单词
转化成最小割。
先讲一下建图方法,再解释原因,我们规定字典序小的是正串,字典序大的是反串:
把可能出现的正串和反串分别建一个点,把他们之间连一条容量为 2 2 2的边,表示断开这条边花费 2 2 2块钱,后面我们要保证断开边意味着正反串均会出现。
把一定会出现的正串和原点连一条容量为 i n f inf inf的边,把一定会出现的反串和汇点连一条容量为 i n f inf inf的边,你可以想象如果正串和反串都连了这种边那就必须要花钱,我们继续考虑无法确定的串的影响。
注意到题目中一个至关重要的条件:按照确定后的阅读顺序读出的所有单词同时满足“自己的字典序不小于翻转后的字典序”,或同时满足“自己的字典序不大于翻转后的字典序”
,这就说明每一行(列)读出来一定同是正串或同是反串。我们对每一个无法确定的行(列)建一个点,把反串连向这个点,这个点连向正串,容量均为
i
n
f
inf
inf。解释一下这种连法的正确性,考虑如果一个正串和反串的边没有断开,且正串是一定会出现的,那么这一行(列)一定会选正串,我们就会把流量注入这一行列中的其他正串,那么其他反串如果被确定了就会产生花费,如果没被确定还是会如上文所述继续决策,故此连边方法正确。
建完图后然后 d i n i c dinic dinic最大流即可,你看 n , m n,m n,m这么小,复杂度肯定是真的啦,连边方法作者觉得已经讲清楚了,具体实现应该不难了吧,还有不懂请参考我的代码 q w q qwq qwq。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#include <map>
using namespace std;
#define inf 0x3f3f3f3f
const int M = 705;
int read()
{
int x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,m,cnt,ans,tot,S,T,f[M],dis[M];
int r[M],c[M];
char s[M][M];
queue<int> q;
set<string> p;
map<string,int> id;
struct edge
{
int v,c,next;
edge(int V=0,int C=0,int N=0) : v(V) , c(C) , next(N) {}
} e[M*M];
void add_edge(int u,int v,int c)
{
printf("%d %d\n",u,v);
e[++tot]=edge(v,c,f[u]),f[u]=tot;
e[++tot]=edge(u,0,f[v]),f[v]=tot;
}
int bfs()
{
memset(dis,0,sizeof dis);
dis[S]=1;
q.push(S);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=f[u]; i; i=e[i].next)
{
int v=e[i].v;
if(e[i].c>0 && !dis[v])
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
if(!dis[T]) return 0;
return 1;
}
int dfs(int u,int ept)
{
if(u==T) return ept;
int flow=0,tmp=0;
for(int i=f[u]; i; i=e[i].next)
{
int v=e[i].v;
if(dis[u]+1==dis[v] && e[i].c)
{
tmp=dfs(v,min(e[i].c,ept));
if(!tmp) continue;
ept-=tmp;
e[i].c-=tmp;
e[i^1].c+=tmp;
flow+=tmp;
if(!ept) break;
}
}
return flow;
}
void work(string s,int tp,int u)
{
string a,b;
int len=s.length();
for(int i=0,r,flag; i<len; i=r+1)
{
r=i;
if(s[i]=='_') continue;
while(r+1<len && s[r+1]!='_') r++;
a=s.substr(i,r-i+1);
b=a;
reverse(b.begin(),b.end());
if(b<a) swap(a,b),flag=-1;
else flag=1;
if(a==b)
{
p.insert(a);
continue;
}
if(!id.count(a))
{
id[a]=++cnt;
id[b]=++cnt;
add_edge(id[a],id[b],2);
}
flag*=tp;
if(flag==1) add_edge(S,id[a],inf);
else if(flag==-1) add_edge(id[b],T,inf);
else add_edge(id[b],u,inf),add_edge(u,id[a],inf);
}
}
void solve()
{
p.clear();
id.clear();
n=read();
m=read();
cnt=n+m;
for(int i=1; i<=n; i++) r[i]=read();
for(int i=1; i<=m; i++) c[i]=read();
for(int i=1; i<=n; i++) scanf("%s",s[i]+1);
S=0;
T=M-1;
string tmp;
for(int i=1; i<=n; i++)
{
tmp=s[i]+1;
work(tmp,r[i],i);
}
for(int i=1; i<=m; i++)
{
tmp="";
for(int j=1; j<=n; j++)
tmp+=s[j][i];
work(tmp,c[i],i+n);
}
while(bfs()) ans+=dfs(S,inf);
printf("%d\n",ans+(int)(p.size()));
}
int main()
{
int Cases=read();
while(Cases--)
{
tot=1;
ans=0;
memset(f,0,sizeof f);
solve();
}
}