zoukankan      html  css  js  c++  java
  • JZOJ 3348. 【NOI2013模拟】秘密任务(最短路+最小割唯一性)

    JZOJ 3348. 【NOI2013模拟】秘密任务

    题目

    Description

    在这里插入图片描述

    Input

    第一行 包含一个正整数T,表示有T组测试数据。接下来依次是T组测试数据。
    每组测试数据的第一行包含两个整N、M。
    第二行包含 N - 1个正整数,依次表示 A1,A2, …,AN-1。
    接下来 M行,每行为三个整数:ui、vi、ci,表示一条连接城市ui和城市vi的路程等于ci的高速公路。

    Output

    输出 T行, 依次表示每组测试数据的答案。若最优方案唯一则输出“Yes”和最小代价,否则输出“No”和最小代价。字符串和整数之间请用一个空格隔开 。

    Sample Input

    3
    3 3
    2 4
    1 3 23
    3 2 12
    2 1 11
    4 4
    3 2 2
    1 2 1
    2 3 1
    3 4 1
    4 1 1
    3 4
    3 2
    1 2 1
    2 3 2
    2 3 19
    3 1 4

    Sample Output

    Yes 4
    Yes 3
    No 2

    Hint

    第 1组测试数据:最优 方案是在城市1设立两个检查点。
    第 2组测试数据:最优方案是城市1的高速公路 (1, 4 )的出入口设立检查点。
    第 3组测试数据:最优方案是在城市2设立一个检查点,不过既可以设置在高速公路(1, 2)的出入口,也可以设置在高速公路(2, 3)的出入口 。

    Data Constraint

    对于 10% 的数据: 2≤N≤10 , 1≤M≤20。
    另有 40% 的数据: 最优方案是唯一的。
    对于 100% 的数据: 2≤N≤400, 1≤M≤4000,1≤T≤5,1≤Ai, c≤10^9。无向图可能有重边 。

    题解

    • 这是一道好题,内涵丰富,值得学习,并且反思总结。
    • 以下内容是详细的思考过程,其中也包含不少他人的指导,十分可贵。
    • 首先分析题目,要求走的是最短路,那么先建出最短路图,
    • 方法一般为,
    • n n n开始做一边单源最短路,然后从 1 1 1开始遍历,
    • 设当前的点为 x x x,当前边权值为 l n ln ln,指向的点为 y y y
    • 仅当 d i s [ x ] = d i s [ y ] + l n dis[x]=dis[y]+ln dis[x]=dis[y]+ln才说明这是最短路图中的边(易证),然后把这条边加入最短路图,接着走向 y y y继续遍历。
    • 其中要注意,因为每个点与每条边可能会走过多次,为了防止重复,必须将已经过的点标记
    • 其实也可以用另一种方法代替这种遍历,
    • 直接搜索每一条变,只要满足 d i s [ x ] = d i s [ y ] + l n dis[x]=dis[y]+ln dis[x]=dis[y]+ln就加入最短路图,
    • (不用判断 d i s [ y ] = d i s [ x ] + l n dis[y]=dis[x]+ln dis[y]=dis[x]+ln,因为如果这样同一条边会被加入两次)
    • 记得上面的最短路图是有向的
    • 题目要求 1 1 1 n n n无法联通,显而易见,这就是求最小割模型,
    • 将最短路图中每条边的权值赋为 m i n ( a [ x ] , a [ y ] ) min(a[x],a[y]) min(a[x],a[y]),当然,如果其中一个点为 n n n,那么权值只能取另一点的 a a a值,
    • 再根据最大流等于最小割,就可以计算出答案了。
    • 然而这样并没有结束,还需要判断答案的唯一性,怎么做?
    • 这就相当于判断最小割的唯一性
    • 不一定!
    • 但首先需要判断最小割的唯一性,
    • 可以找出所有必割的边,当且仅当必割的边权值之和为最小割,才说明最小割的方案是唯一的,
    • 问题来了,哪些边是必割的?
    • 有一个不是特别显然的性质:
    • 如果有向边 ( u , v ) (u,v) (u,v)必割则残余网络中存在至少一条从 1 1 1 u u u的路径和一条从 v v v n n n的路径,
    • 不妨这样想,如果不存在这样的路径,那么 ( u , v ) (u,v) (u,v)就不是必割的(尽管不是特别严谨)。
    • 还有一种判断的方法(来自网络):
    • 设从 1 1 1沿残余网络能到达的点的集合为 S S S,所有能沿残余网络到达 n n n的点的集合为 T T T,总的点集(不是读入的,是最短网络中的)为 V V V
    • S ∪ T = V S∪T=V ST=V那么不唯一,否则唯一。(感性理解)
    • 于是对于前一种判定的方法,先把 1 1 1能走到的和能走到 n n n的标记上,
    • 搜一边每条边(不重不漏),符合条件(如上)的将边权加入 s u m sum sum中,
    • 注意这里因为已经跑过了一边网络流,边权的一部分可能会在其反向边内,所以它的边权应该是 l n [ i ] + l n [ i ⊕ 1 ] ln[i]+ln[i⊕1] ln[i]+ln[i1](符号是异或).
    • 如果 s u m ≠ a n s sum≠ans sum=ans,那么一定不唯一。
    • 一般的判断最小割的唯一性,到此已经结束了,但这题还有特殊的地方,
    • 如果一条必割的边 ( u , v ) (u,v) (u,v),满足 a [ x ] = a [ v ] a[x]=a[v] a[x]=a[v],即说明关卡可以设在 u u u也可以设在 v v v,所以也是不唯一的。

    代码

    #include<cstdio>
    #include<cstring>
    using namespace std;
    #define N 410
    #define M 4010
    #define LL long long
    int bz[N],q[M*10],n,m;
    int to[M*2],next[M*2],last[N],len;
    int to1[M*2],next1[M*2],last1[N],len1;
    int cur[N],gap[N],ds[N];
    LL a[N],ln[M*2],ln1[M*2],dis[N];
    int bs[N],bt[N];
    void add(int x,int y,LL c)
    {
    	to[++len]=y;
    	ln[len]=c;
    	next[len]=last[x];
    	last[x]=len;
    }
    void add1(int x,int y,LL c)
    {
    	to1[++len1]=y;
    	ln1[len1]=c;
    	next1[len1]=last1[x];
    	last1[x]=len1;
    }
    LL min(LL x,LL y)
    {
    	return x<y?x:y;
    }
    LL dg(int k,LL flow)
    {
    	if(k==n) return flow;
    	LL have=0;
    	for(int i=cur[k];i;i=next1[i]) 
    		if(ds[to1[i]]+1==ds[k]&&ln1[i])
    		{
    			cur[k]=i;
    			LL now=dg(to1[i],min(flow-have,ln1[i]));
    			ln1[i]-=now,ln1[i^1]+=now,have+=now;
    			if(flow==have) return have;
    		}
    	cur[k]=last1[k];
    	if(!(--gap[ds[k]])) ds[1]=n;
    	++gap[++ds[k]];
    	return have;
    } 
    void dfs(int k)
    {
    	bs[k]=1;
    	for(int i=last1[k];i;i=next1[i]) 
    		if(ln1[i]&&dis[k]>dis[to1[i]]&&!bs[to1[i]]) dfs(to1[i]);
    }
    void dfs1(int k)
    {
    	bt[k]=1;
    	for(int i=last1[k];i;i=next1[i]) 
    		if(ln1[i^1]&&dis[k]<dis[to1[i]]&&!bt[to1[i]]) dfs1(to1[i]);
    }
    int main()
    {
    	int tn,i,j,x,y;
    	LL c;
    	scanf("%d",&tn);
    	while(tn--)
    	{
    		memset(last,0,sizeof(last));
    		memset(last1,0,sizeof(last1));
    		len=len1=1;
    		scanf("%d%d",&n,&m);
    		for(i=1;i<n;i++) scanf("%lld",&a[i]);
    		for(i=1;i<=m;i++)
    		{
    			scanf("%d%d%lld",&x,&y,&c);
    			add(x,y,c),add(y,x,c);
    		}
    		memset(dis,127,sizeof(dis));
    		memset(bz,0,sizeof(bz));
    		dis[n]=0,bz[n]=1,q[1]=n;
    		int l=0,r=1;
    		while(l<r)
    		{
    			int k=q[++l];
    			for(int i=last[k];i;i=next[i]) 
    			{
    				int g=to[i];
    				if(dis[k]+ln[i]<dis[g])
    				{
    					dis[g]=dis[k]+ln[i];
    					if(!bz[g]) bz[g]=1,q[++r]=g;
    				}
    			}
    			bz[k]=0;
    		}
    		for(i=1;i<=n;i++)
    			for(j=last[i];j;j=next[j]) if(dis[to[j]]+ln[j]==dis[i])
    			{
    				if(to[j]==n) add1(i,to[j],a[i]); else add1(i,to[j],min(a[i],a[to[j]]));
    				add1(to[j],i,0);
    			}
    		memset(gap,0,sizeof(gap));
    		memset(cur,0,sizeof(cur));
    		memset(ds,0,sizeof(ds));
    		gap[0]=n;
    		LL s=0;
    		while(ds[1]<n) s+=dg(1,dis[0]);
    		int ok=1;
    		memset(bs,0,sizeof(bs));
    		memset(bt,0,sizeof(bt));
    		dfs(1),dfs1(n);
    		LL ss=0;
    		for(i=1;i<=n;i++) if(ok)
    			for(j=last1[i];j;j=next1[j]) 
    			{
    				int k=to1[j];
    				if(!bs[i]||!bt[k]||dis[i]<dis[k]) continue;
    				ss+=ln1[j]+ln1[j^1];
    				if(k!=n&&a[i]==a[k]) 
    				{
    					ok=0;
    					break;
    				}
    			}
    		if(ss!=s) ok=0;
    		if(ok) printf("Yes "); else printf("No ");
    		printf("%lld
    ",s);
    	}
    	return 0;
    }
    
    哈哈哈哈哈哈哈哈哈哈
  • 相关阅读:
    基于按annotation的hibernate主键生成策略,(本文copy的 七郎's Blog的博客,觉的不错)
    sql server与oracle常用函数对比
    如何将jar包关联到javadoc文档??
    在struts2中,每次修改了struts.xml都要重启tomcat服务器,那么怎么样设置才能修改了struts.xml而不需要重启tomcat的服务器呢??
    单链表的就地逆置
    读书笔记——尽量将引用参数设置为const类型
    二进制中1的个数
    反转单向链表
    二叉树的深度
    C/C++参数入栈顺序
  • 原文地址:https://www.cnblogs.com/LZA119/p/13910072.html
Copyright © 2011-2022 走看看