luogu P4076 [SDOI2016]墙上的句子
题意看了我半天(逃 (应该是我语文太差了)
题意是要确定每一行和每一列的看单词的顺序,使得同时正着出现和反着出现在里面的单词数量最少,每行和每列的性质是这一行所有单词反过来的单词要么字典序大于等于原来的,要么小于等于原来的
首先回文单词一定会出现,可以直接加入答案,以后就不用管了.对于剩下的单词,如果不想让他贡献答案,那就要使的每次读这个单词都是一个样子,例如"ABC" "CBA",那么第一个正着读,第二个反着读答案最小.为了方便,我们只保留单词字典序更小的形式,同时把行列的读到单词字典序更小的方向作为正方向.然后现在问题变成给每一行每一列确定方向,使得所在行列同时有正方向和反方向的单词数量最小(就是给每个集合确定黑/白颜色,使得所在集合中有黑集合和白集合的元素个数最小)
这个可以使用最小割解决,具体是给每行每列建点,如果正方向可以用就从原点\(ps\)向这个点\(i\)连容量为\(1\)的边\((ps,i,1)\),否则连\((ps,i,+\infty)\),如果反方向可以用就从\(i\)向汇点\(pt\)连容量为\(1\)的边\((i,pt,1)\),否则连\((i,pt,+\infty)\),这样如果最后\(i\)在\(pt\)集合就是正方向,否则就是反方向.然后对于每个单词\(j\)建两个点\(a1_j,a2_j\),连边\((ps,a1_j,p),(a2_j,pt,p)(p\)为一个大于\(n+m\)的数\()\),然后对于单词\(j\)所在的行列\(i\),连边\((a1_j,i,+\infty),(i,a2_j,+\infty)\),这样如果一个单词所在行列同时割在\(ps\)或者\(pt\)集合中,就要多割去一个\(p\),不然要多割掉\(2p\)并且给答案加上\(2\)(一个非回文单词要统计两次).最后答案为回文单词个数+\(2(\lfloor\frac{flow}{p}\rfloor-\)非回文单词个数\()\)
#include<bits/stdc++.h>
#define LL long long
#define uLL unsigned long long
using namespace std;
const int N=75,M=N*N*2,inf=1<<29;
int rd()
{
int x=0,w=1;char ch=0;
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+(ch^48);ch=getchar();}
return x*w;
}
int to[M*4],nt[M*4],c[M*4],hd[M],tot=1;
void add(int x,int y,int z)
{
++tot,to[tot]=y,nt[tot]=hd[x],c[tot]=z,hd[x]=tot;
++tot,to[tot]=x,nt[tot]=hd[y],c[tot]=0,hd[y]=tot;
}
int ps,pt,nhd[M],lv[M];
queue<int> q;
bool bfs()
{
for(int i=ps;i<=pt;++i) lv[i]=0;
lv[ps]=1,q.push(ps);
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=hd[x];i;i=nt[i])
{
int y=to[i];
if(c[i]>0&&!lv[y])
{
lv[y]=lv[x]+1;
q.push(y);
}
}
}
return lv[pt];
}
int dfs(int x,int fw)
{
if(x==pt) return fw;
int an=0;
for(int &i=nhd[x];i;i=nt[i])
{
int y=to[i];
if(c[i]>0&&lv[y]==lv[x]+1)
{
int dt=dfs(y,min(fw,c[i]));
c[i]-=dt,c[i^1]+=dt;
fw-=dt,an+=dt;
if(!fw) break;
}
}
return an;
}
int dinic()
{
int an=0,dt=0;
while(bfs())
{
for(int i=ps;i<=pt;++i) nhd[i]=hd[i];
while((dt=dfs(ps,inf))) an+=dt;
}
return an;
}
map<string,int> mp;
vector<int> ls[N*N];
string ss[N*N];
char cc[N][N];
int n,m,ans,w1[N],w2[N],tt;
int main()
{
int T=rd();
while(T--)
{
memset(hd,0,sizeof(hd)),tot=1;
n=rd(),m=rd();
ps=0,pt=n+m+n*m+1;
for(int i=1;i<=n;++i) w1[i]=rd();
for(int i=1;i<=m;++i) w2[i]=rd();
for(int i=1;i<=n;++i) scanf("%s",cc[i]+1);
mp.clear(),tt=0;
for(int i=1;i<=n;++i)
{
string nw,rnw;
bool fg=0;
for(int j=1;j<=m+1;++j)
{
if(cc[i][j]&&cc[i][j]!='_') nw.push_back(cc[i][j]);
else if(!nw.empty())
{
rnw=nw;
reverse(rnw.begin(),rnw.end());
if(!fg&&rnw!=nw)
{
fg=1;
if(rnw<nw) nw=rnw,w1[i]=-w1[i];
}
if(!mp.count(nw)) mp[nw]=++tt,ss[tt]=nw,ls[tt].clear();
ls[mp[nw]].push_back(i);
nw.clear();
}
}
}
for(int j=1;j<=m;++j) cc[n+1][j]=0;
for(int j=1;j<=m;++j)
{
string nw,rnw;
bool fg=0;
for(int i=1;i<=n+1;++i)
{
if(cc[i][j]&&cc[i][j]!='_') nw.push_back(cc[i][j]);
else if(!nw.empty())
{
rnw=nw;
reverse(rnw.begin(),rnw.end());
if(!fg&&rnw!=nw)
{
fg=1;
if(rnw<nw) nw=rnw,w2[j]=-w2[j];
}
if(!mp.count(nw)) mp[nw]=++tt,ss[tt]=nw,ls[tt].clear();
ls[mp[nw]].push_back(j+n);
nw.clear();
}
}
}
for(int i=1;i<=n;++i)
{
add(ps,i,w1[i]>=0?1:inf);
add(i,pt,w1[i]<=0?1:inf);
}
for(int j=1;j<=m;++j)
{
add(ps,j+n,w2[j]>=0?1:inf);
add(j+n,pt,w2[j]<=0?1:inf);
}
int pc=n+m,dt=0;
ans=0;
for(int i=1;i<=tt;++i)
{
string rnw=ss[i];
reverse(rnw.begin(),rnw.end());
if(ss[i]==rnw){++ans;continue;}
dt+=233;
++pc,++pc;
add(ps,pc-1,233),add(pc,pt,233);
sort(ls[i].begin(),ls[i].end());
vector<int>::iterator it;
int la=0;
for(it=ls[i].begin();it!=ls[i].end();++it)
{
int x=*it;
if(x==la) continue;
add(pc-1,x,inf),add(x,pc,inf);
la=x;
}
}
ans+=((dinic()-dt)/233)*2;
printf("%d\n",ans);
}
return 0;
}
luogu P4076 [SDOI2016]墙上的句子的更多相关文章
- [SDOI2016]墙上的句子
题目描述 考古学家发现了一堵写有未知语言的白色墙壁,上面有一个n行m列的格子,其中有些格子内被填入了某个A至Z的大写字母,还有些格子是空白的. 一直横着或竖着的连续若干个字母会形成一个单词,且每一行的 ...
- Solution -「SDOI 2016」「洛谷 P4076」墙上的句子
\(\mathcal{Description}\) Link. (概括得说不清话了还是去看原题吧 qwq. \(\mathcal{Solution}\) 首先剔除回文串--它们一定对答案产 ...
- 【LOJ】#2066. 「SDOI2016」墙上的句子
题解 我一直也不会网络流--orz 我们分析下这道题,显然和行列没啥关系,就是想给你n + m个串 那么我们对于非回文单词之外的单词,找到两两匹配的反转单词(即使另一个反转单词不会出现也要建出来) 具 ...
- 【题解】Luogu P4069 [SDOI2016]游戏
原题传送门 看到这种题,想都不用想,先写一个树链剖分 然后发现修改操作增加的是等差数列,这使我们想到了李超线段树 先进性树剖,然后用李超线段树维护区间最小,这样就做完了(写码很容易出错) 复杂度为\( ...
- Luogu P4070 [SDOI2016]生成魔咒
题目链接 \(Click\) \(Here\) 其实是看后缀数组资料看到这个题目的,但是一眼反应显然后缀自动机,每次维护添加节点后的答案贡献即可,唯一不友好的一点是需要平衡树维护,这里因为复杂度不卡而 ...
- Luogu P4071 [SDOI2016]排列计数
晚上XZTdalao给我推荐了这道数论题.太棒了又可以A一道省选题了 其实这道题也就考一个错排公式+组合数+乘法逆元 我们来一步一步分析 错排公式 通俗的说就是把n个1~n的数排成一个序列A,并使得所 ...
- Luogu 4069 [SDOI2016]游戏
BZOJ 4515 树链剖分 + 李超线段树 要求支持区间插入一条线段,然后查询一个区间内的最小值.可以使用李超线段树解决,因为要维护一个区间内的最小值,所以每一个结点再维护一个$res$表示这个区间 ...
- Luogu P4068 [SDOI2016]数字配对
反正现在做题那么少就争取做一题写一题博客吧 看到题目发现数字种类不多,而且结合价值的要求可以容易地想到使用费用流 但是我们如果朴素地建图就会遇到一个问题,若\(i,j\)符合要求,那么给\(i,j\) ...
- bzoj AC倒序
Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...
随机推荐
- 移动端 iphone手机在中文情况下不执行keyup事件
问题:移动端 在
- javaInt占几个字节
javaInt占几个字节 一个字节等于8位:1 byte = 8 bit. 在java中的中文和英文字母都是采用Unicode编码来表示的,一个Unicode编码为16位,1个字节是8位,所以1个Un ...
- 七、smarty--缓存的控制
1.建议缓存 $smarty->cacheing = true; //设置为2是给每一个模板设置缓存 $smarty->setCacheDir(“”); 2.处理缓存的生命周期 $smar ...
- android 播放音乐媒体文件(一)
Audio formats and codecs Format / Codec Encoder Decoder Details Supported File Type(s) / Container F ...
- StringUtils.isBlank()检验String 类型的变量是否为空
在校验一个String类型的变量是否为空时,通常存在3中情况 是否为 null 是否为 "" 是否为空字符串(引号中间有空格) 如: " ". Str ...
- Iris Classification on Keras
Iris Classification on Keras Installation Python3 版本为 3.6.4 : : Anaconda conda install tensorflow==1 ...
- linux(centOS7)的基本操作(三) 用户、组、权限管理
用户和组 1.用户.组.家目录的概念 linux系统支持多用户,除了管理员,其他用户一般不应该使用root,而是应该向管理员申请一个账号.组类似于角色,系统可以通过组对有共性的用户进行统一管理.每个用 ...
- web开发(一)-Servlet详解
在网上看见一篇不错的文章,写的详细. 以下内容引用那篇博文.转载于<http://www.cnblogs.com/whgk/p/6399262.html>,在此仅供学习参考之用. 一.什么 ...
- Oracle中 ORA-12704:字符集不匹配
前言 在使用Union all连接时,若A集合中某列为nvarchar2或nvarchar类型,而B集合中无此列,用‘ ’ 来代替是会报字符集不匹配 1 select '中国','China',cas ...
- [BeiJingWc2008]Gate Of Babylon
<基尔伽美修>是人类历史上第一部英雄史诗,两河流域最杰出的文学作品之一.作品讲述了基尔伽美修一生的传奇故事.在动画Fate/staynight中,基尔伽美修与亚瑟王等传说中的英雄人物一起出 ...