ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[CSP-S 2021] 交通规划 题解

2022-05-06 17:01:48  阅读:187  来源: 互联网

标签:连通 int 题解 短路 head elen 2021 CSP dp


[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\) 同属一个连通块,于是大致形成了这样的情况:

img

(图源:约瑟夫用脑玩

容易发现红色数量必然和蓝色数量相同(奇数直接合并在一起)

给出一个结论:将一个红色块和一个蓝色块两两配对跑最短路,再去掉这些最短路对应的原图的边集,那么答案方案就是这些边集权值和

可以说明的是,不用担心路径重复的问题,因为如若重复,那么交换一下配对方式肯定可以干掉重复的路径而且显然更优。

结论的证明可以感性理解一下。最后的答案肯定是长成这个形式,因为我如若有一个割是从某个连通块内部出发的,那显然不是很有用;也不会是路径走到一半就断掉,走一半断掉没有起到分割颜色的作用,不如不走。

具体证明可以看:此处 暴力分类讨论。

具体的,由于引入了新点(连通块代表点),可以将相邻连通块之间的权值设为第二个(顺时针)连通块的第一条射线权值(按照上面所述划分连通块方式,这必定是一个特殊射线)

对于连通块内的射线之间,边权 \(0\) 好了,以防止这样的情况:

pic1_1

(图源: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;
}

标签:连通,int,题解,短路,head,elen,2021,CSP,dp
来源: https://www.cnblogs.com/wyb-sen/p/16229507.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有