1. 程式人生 > >【學術篇】SDOI2009 最優影象

【學術篇】SDOI2009 最優影象

又是一道辣雞卡常數題….
luogu上有些題的時限還是有毒的… 最後也只能靠O2過掉了…
不過給我原題當時的2s我隨便過給你看嘛, 哪怕評測姬慢50%都沒關係的.. 貼一下codevs的截圖…
這裡寫圖片描述
你看最慢的點也就1.07s…… (畢竟程式自帶大常數←_←

好了不吐槽了, 我們來分析一下這道題吧…
其實我當時做的時候(好像還是做學校食堂的那次測試?)並不知道這是一道網路流…
然後就寫暴力滾粗…

但據說這是一種非常常見的建圖方式.. 我們還是分析題目條件.
我們在網路流裡面做到的題目都是求和的, 那麼我們就要想辦法把Π轉換為.
轉換完之後就可以跑最大費用最大流了.那麼怎麼轉換呢?? 用對數!!!


我們高一的時候學過, logaxy=logax+logay(a>0,a1), 這樣我們就可以建邊了.

  • 每一行, 每一列作為一個點, 分別放在兩邊.
  • 行和列之間連一條流量為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(); }

最後, 卡常數是非常不對的一件事情…或者說我該去化驗血統了?