【学术篇】SDOI2009 最优图像

又是一道辣鸡卡常数题….
luogu上有些题的时限还是有毒的… 最后也只能靠O2过掉了…
不过给我原题当时的2s我随便过给你看嘛, 哪怕评测姬慢50%都没关系的.. 贴一下codevs的截图…
这里写图片描述
你看最慢的点也就1.07s…… (毕竟程序自带大常数←_←

好了不吐槽了, 我们来分析一下这道题吧…
其实我当时做的时候(好像还是做学校食堂的那次测试?)并不知道这是一道网络流…
然后就写暴力滚粗…

但据说这是一种非常常见的建图方式.. 我们还是分析题目条件.
我们在网络流里面做到的题目都是求和的, 那么我们就要想办法把Π转换为.
转换完之后就可以跑最大费用最大流了.那么怎么转换呢?? 用对数!!!
我们高一的时候学过,log(xy)=log x+log y(底大于0且不等于1), 这样我们就可以建边了.

  • 每一行, 每一列作为一个点, 分别放在两边.
  • 行和列之间连一条流量为1, 费用为交点的概率的对数的边, 表示这个点最多选一次, 选的话取这个概率.
  • 源点向每个行对应的点连流量为这一行的黑像素数, 费用为0的边, 表示这一行要选这么多个.
  • 每个列对应的点向汇点连流量为这一列的黑像素数, 费用为0的边, 表示这一列要选这么多个.

样例建图大约就是这个样子(好像还并不是很清楚怎么建嘛) (对数的底我就随便取个e算了)
这里写图片描述

然后费用取反跑费用流就行了. 据说这个题只能用zkw过, 但我的zkw也没过

代码:

#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=205;
const int M=23333;
const int INF=0x7f7f7f7f;
inline int gn(int a=0,char c=0){
    for(;c<'0'||c>'9';c=getchar());
    for(;c>47&&c<58;c=getchar())a=a*10+c-48;return a;
}
int v[N],nxt[M],to[M],fl[M],co[M],tot=1;
void buildedge(int x,int y,int flow,int cost){
    to[++tot]=y; nxt[tot]=v[x]; v[x]=tot; fl[tot]=flow; co[tot]=cost;
    to[++tot]=x; nxt[tot]=v[y]; v[y]=tot; fl[tot]=0; co[tot]=-cost;
}
int d[N],n,m,s,t,cost,p[N][N],a[N],b[N];
int q[M],H,T,i,j,k;
bool vis[N];
inline bool spfa(int s,int t){
    memset(vis,0,sizeof(vis));
    memset(d,0x7f,sizeof(d));
    d[t]=0; H=0; T=1; vis[t]=1; q[T]=t;
    while(H<T){
        int x=q[++H]; vis[x]=0;
        for(int i=v[x];i;i=nxt[i])
            if(fl[i^1]&&d[to[i]]>d[x]-co[i]){
                d[to[i]]=d[x]-co[i];
                if(!vis[to[i]]) vis[to[i]]=1,q[++T]=to[i];
            }
    }
    return d[s]<INF;
}
int dfs(int x,int mx,int s=0){ vis[x]=1;
    if(x==t) return mx; int k;
    for(int i=v[x];i;i=nxt[i])
        if(!vis[to[i]]&&fl[i]&&d[to[i]]==d[x]-co[i]){
            k=::dfs(to[i], mx-s>fl[i]?fl[i]:mx-s);
            if(k) cost+=k*co[i],fl[i]-=k,fl[i^1]+=k,s+=k;
            if(s==mx) break;
        }
    return s;
}
int mcmf(int flow=0){
    while(::spfa(s, t)){ vis[t]=1;
        while(vis[t]){
            memset(vis,0,sizeof(vis));
            flow+=::dfs(s, INF);
        }
    } return flow;
}
void findans(){
    for(int i=1;i<=n;++i)
        for(int j=v[i];j;j=nxt[j])
            if(to[j]>n)
                p[i][to[j]-n]=!fl[j];
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j)
            putchar(48+p[i][j]);
        putchar(10);
    }
}
int main(){
    n=gn(); m=gn(); s=0; t=n+m+1;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j){
            int k=gn();
            if(k) ::buildedge(i, j+n, 1, (int)(-log2(k)*2333333));
        }
    for(int i=1;i<=n;++i)
        ::buildedge(s, i, gn(), 0);
    for(int i=1;i<=m;++i)
        ::buildedge(i+n, t, gn(), 0);
    mcmf(); findans();
}

最后, 卡常数是非常不对的一件事情…或者说我该去化验血统了?

posted @ 2018-01-31 21:15  Enzymii  阅读(172)  评论(0编辑  收藏  举报