[SDOI2016]墙上的句子

一、题目

点此看题

二、解法

没有什么好的做法,那就考虑网络流把,首先可以单独处理回文串的情况(贡献为 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();
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值