1. 程式人生 > 其它 >[CSP-S 2021] 交通規劃 題解

[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;
}