列表

详情


NC229950. [CSP2021]交通规划(traffic)

描述

给定一个平面上n条水平直线和m条垂直直线,它们相交形成nm列的网格, 从上到下第r条水平直线和从左到右第c条垂直直线之间的交点称为格点(r, c)。网格中任意两个水平或垂直相邻的格点之间的线段称为一条边,每条边有一个非负整数边权。

进行T次询问,每次询问形式如下:

给出kT次询问的k可能不同)个附加点,每个附加点位于一条从网格边缘向外出发的射线上。所有从网格边缘向外出发的射线按左上-右上-右下-左下-左上的顺序依  次编号为1,如下图:

对于每次询问,不同附加点所在的射线互不相同。每个附加点和最近的格点之间的线段也称为一条边,也有非负整数边权(注意,在角上的格点有可能和两个附加点同时相连)。

给定每个附加点的颜色(黑色或者白色),请你将网格内每个格点的颜色染成黑白二者之一,并使得所有两端颜色不同的边的边权和最小。请输出这个最小的边权和。
traffic.zip

输入描述

第一行3个正整数n, m ,T分别表示水平、垂直直线的数量,以及询问次数。

接下来行,每行m个非负整数。其中第i行的第j个非负整数表示(i, j)间的边权。

接下来n行,每行个非负整数。其中第i行的第j个非负整数表示(i, j)间的边权。

接下来依次输入T组询问。第i组询问开头为一行一个正整数k_i表示这次询问附加点的总数。接下来k_i行每行三个非负整数。其中第j行依次为表示第i个附加点和相邻格点之间的边权、所在的射线编号以及附加点颜色(0 为白色,1 为黑色)。保证同一组询问内互不相同。

每行的多个整数由空格分隔。

输出描述

输出T行,第i行输出一个非负整数,表示第i次询问染色之后两端颜色不同的边权和的最小值。

示例1

输入:

2 3 1
9 4 7
3 8
10 5
2
19 3 1
17 9 0

输出:

12

说明:

最优方案:(1,3),(1,2),(2,3) 为黑色;(1,1),(2,1),(2,2) 为白色。

原站题解

上次编辑到这里,代码来自缓存 点击恢复默认模板

C++(clang++ 11.0.1) 解法, 执行用时: 1857ms, 内存消耗: 18908K, 提交时间: 2022-08-03 17:47:51

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
struct Extra
{
	int w,p,c;
	bool operator < (const Extra &b) const
	{
		return p<b.p;
	}
}e[60];
priority_queue<pair<long long,int> > q;
int n,m,T,k,np,tot,no,h[510][510],s[510][510],head[300010],nxt[1200010],to[1200010],val[1200010],p[510][510];
long long ans,d[300010],dis[110][110],f[110][110];
inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
void add(int x,int y,int z)
{
	nxt[++tot]=head[x],head[x]=tot,to[tot]=y,val[tot]=z,nxt[++tot]=head[y],head[y]=tot,to[tot]=x,val[tot]=z;
}
int main()
{
	n=read(),m=read(),T=read();
	for(int i=0;i<=n;i++) for(int j=0;j<=m;j++){
		if(i&&j&&i<n) s[i][j]=read();
		p[i][j]=++np;
	}//这段话的目的其实就是预处理出矩形中每一个点对应的编号
	for(int i=1;i<=n;i++) for(int j=1;j<m;j++) h[i][j]=read();
	while(T--){
		tot=no=0,np=(n+1)*(m+1),ans=0x3f3f3f3f3f3f3f3f,k=read();//多测记得清空
		for(int i=1,w,p,c;i<=k;i++){
			e[i].w=w=read(),e[i].p=p=read(),e[i].c=c=read();
			if(p<=m) s[0][p]=w;
			else if(p<=n+m) h[p-m][m]=w;
			else if(p<=m*2+n) s[n][m*2+n-p+1]=w;
			else h[m*2+n*2-p+1][0]=w;//加入附加点与普通点间的边权
		}
		for(int i=0;i<=n;i++) for(int j=0;j<=m;j++){
			if(j>=1) add(p[i][j-1],p[i][j],s[i][j]);
			if(i>=1) add(p[i-1][j],p[i][j],h[i][j]);
		}//建立出矩形
		sort(e+1,e+k+1),e[k+1]=e[1];//附加点顺时针排序
		for(int i=1;i<=k;i++) if(e[i].c!=e[i+1].c){
			no++;
			for(int j=e[i].p;j<e[i+1].p+(i==k?2*n+2*m:0);j++){
				int J=j%(2*n+2*m);
				if(J<=m) add(p[0][J],np+no,0);
				else if(J<=n+m) add(p[J-m][m],np+no,0);
				else if(J<=m*2+n) add(p[n][m*2+n-J],np+no,0);
				else add(p[m*2+n*2-J][0],np+no,0);
			}//加入两个附加点之间的那个可以作为起、终点的点和连边
		}
		for(int i=1;i<no;i++){
			for(int j=1;j<=np+no;j++) d[j]=ans;
			d[np+i]=0,q.push(make_pair(-d[np+i],np+i));
			while(!q.empty()){
				int x=q.top().second,temp=q.top().first;q.pop();
				if(temp!=-d[x]) continue;
				for(int j=head[x];j;j=nxt[j]) if(d[to[j]]>d[x]+val[j]) d[to[j]]=d[x]+val[j],q.push(make_pair(-d[to[j]],to[j]));
			}
			for(int j=i+1;j<=no;j++) dis[i][j]=dis[i][j+no]=dis[j][i+no]=dis[i+no][j+no]=d[np+j];//要记得是环状的结构所以要开两倍
		}//求最短路,这没什么好说的吧
		for(int i=1;i<=2*no;i++) f[i][i]=ans,f[i][i-1]=0;//记得初始化
		for(int i=2;i<=no;i++) for(int j=1;j+i-1<=2*no;j++){
			int l=j,r=j+i-1;
			f[l][r]=f[l+1][r-1]+dis[l][r];
			for(int p=l+1;p<r;p++) f[l][r]=min(f[l][r],f[l][p]+f[p+1][r]);
			if(i==no) ans=min(ans,f[l][r]);
		}//区间DP求出最优解
		for(int i=1,p,c;i<=k;i++){
			p=e[i].p,c=e[i].c;
			if(p<=m) s[0][p]=0;
			else if(p<=n+m) h[p-m][m]=0;
			else if(p<=m*2+n) s[n][m*2+n-p+1]=0;
			else h[m*2+n*2-p+1][0]=0;
		}//多测清空
		for(int i=1;i<=np+no;i++) head[i]=0;//多测清空
		printf("%lld\n",ans==0x3f3f3f3f3f3f3f3f?0:ans);//记得特判
	}
	return 0;
}

上一题