[CSP-S 2021] 交通規劃 題解
[CSP-S 2021] 交通規劃 題解
對偶圖網路流+區間 DP
CCF一到店,所有 OIer 便都看著他笑,有的故意的高聲嚷道,“你的題一定又超綱了!”CCF睜大眼睛說,“你怎麼這樣憑空汙人清白……”“什麼清白?我去年 CSP T4 啥都不會,被吊著打。”CCF便漲紅了臉,額上的青筋條條綻出,爭辯道,“題難不能超綱……題難!……出題的事,能算超綱麼?
去年的題現在才來補 qwq
還記得考前教練說決定不可能考網路流,哈哈
考場上不知道在幹什麼,50 分的 \(k=2\) 都不會 qwq,wtcl
嚴格來說也沒有超綱,畢竟程式碼裡面只有最短路和 DP 嘛
Statement
P7916 [CSP-S 2021] 交通規劃 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
Solution
考慮一個 01 變數 \(x\),將 $x=0 $ 視為其對應點與源點聯通,\(x=1\) 視為與匯點聯通。那麼考慮最小割中每條邊的貢獻:對於形如 \((S,x,a)\) 的邊,當且僅當 \(x\) 與匯點聯通時這條邊才會有 \(a\) 的貢獻,於是可以將這條邊的貢獻記為 \(ax\) 。同理,形如 \((x,T,a)\) 的邊可看成 \(a(1-x)\),而形如 \((x,y,a)\) 的邊則可看成 \(a(1-x)y\)。對於某些問題,我們可以將貢獻拆成上述三種形式,便可以通過跑最小割求解。
——Bindir0
容易發現轉對偶圖之後 \(k=2\) 就是一個最小割的事情,dij 就可以了
naive 的,我們猜想 \(k>2\) 是不是直接設一個超級源點,超級匯點就可以了
發現不是很刑,你這樣割,把白點割到匯點所屬集合裡面怎麼辦
這樣做還有一個問題是,因為我們是要先轉化對偶圖的(直接在原圖上跑會 T 飛),而路徑可能會交叉,那咋轉對偶圖
我們考慮把問題轉化到 \(k=2\) 的情況。
首先,我們順時針把射線連起來,同時,從某一個特殊點開始,如若 \(i+1\) 不是特殊點/顏色和 \(i\) 一樣,那麼我們認為 \(i,i+1\) 同屬一個連通塊,於是大致形成了這樣的情況:
(圖源:約瑟夫用腦玩)
容易發現紅色數量必然和藍色數量相同(奇數直接合並在一起)
給出一個結論:將一個紅色塊和一個藍色塊兩兩配對跑最短路,再去掉這些最短路對應的原圖的邊集,那麼答案方案就是這些邊集權值和
可以說明的是,不用擔心路徑重複的問題,因為如若重複,那麼交換一下配對方式肯定可以幹掉重複的路徑而且顯然更優。
結論的證明可以感性理解一下。最後的答案肯定是長成這個形式,因為我如若有一個割是從某個連通塊內部出發的,那顯然不是很有用;也不會是路徑走到一半就斷掉,走一半斷掉沒有起到分割顏色的作用,不如不走。
具體證明可以看:此處 暴力分類討論。
具體的,由於引入了新點(連通塊代表點),可以將相鄰連通塊之間的權值設為第二個(順時針)連通塊的第一條射線權值(按照上面所述劃分連通塊方式,這必定是一個特殊射線)
對於連通塊內的射線之間,邊權 \(0\) 好了,以防止這樣的情況:
(圖源:Piwry,我怎麼在到處嫖圖 /fn
為了找到最優的配對方式,我們可以執行一個區間 DP 的過程
首先管都不管,跑 \(k\) 次最短路,預處理陣列 \(dis[x][y]\) 表示第 \(x\) 個特殊點到第 \(y\) 個特殊點的最短路
然後設 \(dp[l][r]\) 表示匹配區間 \([l,r]\) 中的點的最小答案,注意這裡破環為鏈
轉移:\(dp[l][r]=min(dp[l+1][r-1]+dis[l][r],\min \{dp[l][k]+dp[k+1][r]\})\)
轉移的時候特別注意一定時刻保證使用的 dp 值所代表的區間長度是偶數
所以總複雜度 \(O(\sum k (nm)\log (nm)+\sum k^3)\)
Code
程式碼學習自:Piwry
#include<bits/stdc++.h>
#define id(i,j) ((i)*(m+1)+(j))
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
typedef long long ll;
const int N = 5e2+5;
const int K = 55;
char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){
int s=0,w=1; char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
return s*w;
}
bool cmin(ll&a,ll b){return a>b?a=b,1:0;}
bool cmin(int&a,int b){return a>b?a=b,1:0;}
struct Dijkstra{
struct Edge{int nex,to,dis;}edge[N*N*4];
priority_queue<pii,vector<pii>,greater<pii> >q;
int head[N*N],orihead[N*4*2],orid[N*4*2];
//雙向邊兩倍,一共 4n 條射線,每條射線像所屬連通塊連邊
int dis[N*N],vis[N*N];
int elen,orielen;
void addedge(int u,int v,int w){
edge[++elen]=(Edge){head[u],v,w},head[u]=elen;
edge[++elen]=(Edge){head[v],u,w},head[v]=elen;
// cout<<u<<" "<<v<<" "<<w<<endl;
}
void addedge2(int u,int v,int w){
orihead[++orielen]=head[u],orid[orielen]=u;
edge[++elen]=(Edge){head[u],v,w},head[u]=elen;
// cout<<u<<" "<<v<<" "<<w<<endl;
}
void reset(){//卡常嘎嘎嘎,原圖上的邊重複用
elen-=orielen;
while(orielen)
head[orid[orielen]]=orihead[orielen],
orielen--;
}
void dijkstra(int s){//dijkstra
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
dis[s]=0,q.push(pii(0,s));
while(q.size()){
int u=q.top().se; q.pop();
if(vis[u])continue; vis[u]=1;
for(int e=head[u],v;v=edge[e].to,e;e=edge[e].nex)
if(cmin(dis[v],dis[u]+edge[e].dis))q.push(pii(dis[v],v));
}
}
}graph;
struct Point{int w,p,t;}node[K];
int pos[N<<2],dis[K][K];
ll dp[K<<1][K<<1];
vector<int>vec;
int n,m,T,siz;
signed main(){
n=read(),m=read(),T=read(),siz=(n+1)*(m+1);
for(int i=1;i<n;++i)for(int j=1,w;j<=m;++j)
w=read(),graph.addedge(id(i,j-1),id(i,j),w);//id(i,j) 是對偶圖下標
for(int i=1;i<=n;++i)for(int j=1,w;j<m;++j)
w=read(),graph.addedge(id(i-1,j),id(i,j),w);
for(int i=1;i<=m;++i)pos[i]=id(0,i);
for(int i=m+1;i<=n+m;++i)pos[i]=id(i-m,m);
for(int i=n+m+1;i<=n+2*m;++i)pos[i]=id(n,n+2*m-i);
for(int i=n+2*m+1;i<=2*n+2*m;++i)pos[i]=id(2*n+2*m-i,0);//順時針處理射線位置
//for(int i=1;i<=2*n+2*m;++i)cout<<pos[i]<<" ";puts("");
while(T--){
int k=read(),nw=siz;
for(int i=1;i<=k;++i)
node[i].w=read(),node[i].p=read(),node[i].t=read();
sort(node+1,node+1+k,[](Point x,Point y){return x.p<y.p;});
// for(int i=1;i<=k;++i)
// cout<<node[i].p<<" "<<node[i].w<<" "<<node[i].t<<endl;
node[k+1]=node[1],vec.clear();
for(int i=1;i<=k;++i){
for(int j=node[i].p;j!=node[i+1].p;j=j%(2*m+2*n)+1)
graph.addedge2(nw,pos[j],0),graph.addedge2(pos[j],nw,0);
if(i<k)graph.addedge2(nw,nw+1,node[i+1].w),graph.addedge2(nw+1,nw,node[i+1].w);
else graph.addedge2(nw,siz,node[1].w),graph.addedge2(siz,nw,node[1].w);
if(node[i].t!=node[i+1].t)vec.push_back(nw);
nw++;
}
if(vec.size()==1)puts("0");//特判 k=1
else{
for(auto u:vec){
graph.dijkstra(u);
for(auto v:vec)
dis[u-siz][v-siz]=graph.dis[v];
}
int tmp=vec.size();
for(int i=0;i<tmp;++i)vec.push_back(vec[i]);
memset(dp,0x3f,sizeof(dp));
for(int i=0;i+1<tmp*2;++i)//破環為鏈
dp[i][i+1]=dis[vec[i]-siz][vec[i+1]-siz];
for(int len=4;len<=tmp;len+=2)//注意控制區間長度
for(int l=0;l+len-1<tmp*2;++l){
int r=l+len-1;
dp[l][r]=dp[l+1][r-1]+dis[vec[l]-siz][vec[r]-siz];
for(int i=l+1;i<=r-2;i+=2)
cmin(dp[l][r],dp[l][i]+dp[i+1][r]);
}
ll ans=1e18;
for(int i=0;i-1<tmp;++i)
cmin(ans,dp[i][i+tmp-1]);
printf("%lld\n",ans);
}
graph.reset();
}
return 0;
}