題解 [SDOI2016]牆上的句子
題解 [SDOI2016]牆上的句子
題目分析
發現數據範圍很小,考慮使用網路流。
首先,對於迴文串單詞,無論正著讀還是反著讀都要對答案貢獻一,所以先處理所有迴文串單詞,然後再刪除這些迴文串單詞去考慮其他單詞。有了迴文串,那麼一個單詞的字典序要麼嚴格大於它的反串要麼嚴格小於它的反串。
將某個單詞和它的反串一起考慮,如果這個單詞和其反串都出現了,那麼會對答案有 2 的貢獻,這啟發我們使用最小割,不妨假設這個單詞的字典序嚴格小於它的反串,那麼我們認為這個單詞出現在了最終的字典中當且僅當源點 \(S\) 可以到達這個單詞,其反串出現在了最終的字典中當且僅當這個單詞可以到達匯點 \(T\) ,然後這個單詞和其反串有一條流量為 2 的邊,由此建圖可以得到如果這個單詞和其反串同時出現就必須要割掉這兩個點之間的邊,產生 2 的貢獻。
按照上面的思路建出來的圖大概是這樣:
考慮如何用給定的矩陣去限制這些點和源點 \(S\) 匯點 \(T\) 之間的連通性。考慮到題目保證從某一個位置去看得到的單詞要麼字典序嚴格小於反串要麼嚴格大於反串,所以某一行或者列從某個方向看相當於是讓某些在上面的點和 \(S\) 連通或者是讓某些在下面的點和 \(T\) 連通。
某一行要麼從左往右看要麼從右往左看,不妨假設從左往右看得到的單詞的字典序都嚴格小於其反串,可以使用一個經典模型,給每個要求建一個節點,然後 \(S\) 直接和這個節點連邊,這個節點和 \(T\) 直接連邊,我們欽定如果這個要求和 \(S\) 的連邊沒有被割斷,那麼表示它從左往右看,否則表示它從右往左看,然後我們就可以根據這個欽定的方法去給要求節點和單詞連邊了,為了保證要求節點只會割斷一邊,可以給這個流量設定一個極大值 \(x\)
舉個例子吧,最後建出來的圖大概是這樣的:
解釋一下這張圖是什麼意思:設 1,3,5 號節點分別表示單詞 AB
CD
EF
,那麼 2,4,6 號節點分別表示單詞 BA
DC
FE
,那麼 7 號節點從左往右看的單詞就是 AB,EF
, 8 號節點從左往右看的單詞就是 CD
, 9 號節點從左往右看的單詞就是 AB
,其中 7 號節點要求必須從左往右看, 8 號節點要求必須從右往左看, 9 號節點任意,那麼我們建出來的圖就是上面的樣子,可以看出來,上面的圖只需要割斷 9 和匯點 T 連著的邊就行了。
參考程式碼
#include<set>
#include<map>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ch() getchar()
#define pc(x) putchar(x)
#define inf 10000
#define INF 1000000000
using namespace std;
template<typename T>void read(T&x){
static char c;static int f;
for(c=ch(),f=1;c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c>='0'&&c<='9';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>void write(T x){
static char q[65];int cnt=0;
if(x<0)pc('-'),x=-x;
q[++cnt]=x%10,x/=10;
while(x)
q[++cnt]=x%10,x/=10;
while(cnt)pc(q[cnt--]+'0');
}
const int maxn=10005,maxm=1000005;
struct Edge{
int v,w,nt;
Edge(int v=0,int w=0,int nt=0):
v(v),w(w),nt(nt){}
}e[maxm];
int hd[maxn],num=1;
void qwq(int u,int v,int w){
e[++num]=Edge(v,w,hd[u]),hd[u]=num;
}
void qvq(int u,int v,int w){
qwq(u,v,w);qwq(v,u,0);
}
int tot,S=0,T=1,dis[maxn],q[maxn];
int bfs(void){
memset(dis,0,(tot+1)*4);
int fro=0,bac=0;dis[q[bac++]=S]=1;
while(fro<bac){
int u=q[fro++];
for(int i=hd[u];i;i=e[i].nt){
int v=e[i].v,w=e[i].w;
if(w&&!dis[v])
dis[q[bac++]=v]=dis[u]+1;
}
}
return dis[T];
}
int cur[maxn];
int dfs(int u,int ep){
if(u==T)return ep;int re=0;
for(int&i=cur[u];i;i=e[i].nt){
int v=e[i].v,w=e[i].w;
if(w&&dis[v]==dis[u]+1){
int tmp=dfs(v,min(ep,w));
re+=tmp;ep-=tmp;
e[i^1].w+=tmp;e[i].w-=tmp;
if(!ep)break;
}
}
return re;
}
int dinic(void){
int re=0;
while(bfs()){
memcpy(cur,hd,(tot+1)*4);
re+=dfs(S,INF);
}
return re;
}
string ReVerSe(string s){
string re="";int len=s.length();
for(int i=0;i<len;++i)re=s[i]+re;
return re;
}
set<string>s;
map<string,int>sp;
int row[80],col[80],rvr[80],rvc[80];
char mp[80][80];
int main(){
int test;read(test);
while(test--){
int n,m;
read(n),read(m);
for(int i=1;i<=n;++i)read(row[i]);
for(int i=1;i<=m;++i)read(col[i]);
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
char c=ch();
while(c!='_'&&(c<'A'||c>'Z'))c=ch();
mp[i][j]=c;
}
}
s.clear();sp.clear();
for(int i=1;i<=n;++i){
rvr[i]=true;
mp[i][m+1]='_';
string str="";
for(int j=1;j<=m+1;++j){
if(mp[i][j]=='_'){
if(str!=""){
string rts=ReVerSe(str);rvr[i]&=(rts<=str);
s.insert(str),s.insert(rts);
}
str="";
}
else str=str+mp[i][j];
}
}
for(int j=1;j<=m;++j){
rvc[j]=true;
mp[n+1][j]='_';
string str="";
for(int i=1;i<=n+1;++i){
if(mp[i][j]=='_'){
if(str!=""){
string rts=ReVerSe(str);rvc[j]&=(rts<=str);
s.insert(str),s.insert(rts);
}
str="";
}
else str=str+mp[i][j];
}
}
tot=1;int ans=0;
memset(hd,0,sizeof hd);num=1;
for(set<string>::iterator it=s.begin();it!=s.end();++it){
string abc=*it,cba=ReVerSe(abc);
if(abc==cba)++ans;
else if(abc<cba&&s.find(cba)!=s.end()){
sp[abc]=++tot;sp[cba]=++tot;
qvq(tot-1,tot,2);
}
}
for(int i=1;i<=n;++i){
++tot;
if(!row[i])/*continue*/;
else if((row[i]==1)^(rvr[i]))
qvq(S,tot,inf);
else
qvq(tot,T,inf);
string str="";
for(int j=1;j<=m+1;++j){
if(mp[i][j]=='_'){
if(str!=""&&sp.find(str)!=sp.end()){
string rts=ReVerSe(str);if(rts<str)swap(rts,str);
qvq(tot,sp[str],inf),qvq(sp[rts],tot,inf);
}
str="";
}
else str=str+mp[i][j];
}
}
for(int j=1;j<=m;++j){
++tot;
if(!col[j])/*continue*/;
else if((col[j]==1)^(rvc[j]))
qvq(S,tot,inf);
else
qvq(tot,T,inf);
string str="";
for(int i=1;i<=n+1;++i){
if(mp[i][j]=='_'){
if(str!=""&&sp.find(str)!=sp.end()){
string rts=ReVerSe(str);if(rts<str)swap(rts,str);
qvq(tot,sp[str],inf),qvq(sp[rts],tot,inf);
}
str="";
}
else str=str+mp[i][j];
}
}
write(dinic()+ans),pc('\n');
}
return 0;
}