【比赛】【2019.8.1】

T1

不是很难,注意积雪高度的判断(要开long long)以及终点不需要特判即可。

#include<cstdio>
#include<cstring>

const int maxn=1e5+2;

struct Solution
{
	struct Edge{int from,to,len;};
	
	struct Graph
	{
		Edge edges[maxn*10];
		int edge_cnt,Head[maxn],Next[maxn*10];
		
		void add_edge(int from,int to,int len)
		{
			edges[++edge_cnt]=(Edge){from,to,len};
			Next[edge_cnt]=Head[from],Head[from]=edge_cnt;return;
		}
	}G;
	
	int bh[maxn],lh[maxn],ad;
	
	long long dis[maxn],vis[maxn];
	int S,T;
	
	int Q[maxn*10];
	
	void SPFA()
	{
		memset(dis,0x3f,sizeof(dis));
		dis[S]=0;int h=0,t=0;Q[t++]=S;
		
		while( h!=t )
		{
			int p=Q[h++];h%=maxn*5;
			vis[p]=0;
			
			for(int i=G.Head[p];i;i=G.Next[i])
			{
				Edge e=G.edges[i];
				
				if( ( e.to==T or bh[e.to]+ad*( (long long)dis[p]+e.len )<=lh[e.to] ) and dis[p]+(long long)e.len<dis[e.to] )
				{
					dis[e.to]=dis[p]+e.len;
					if( !vis[e.to] ) vis[e.to]=1,Q[t++]=e.to,t%=maxn*5;
				}
			}
		}
		
		return;
	}
	
	void solve()
	{
		int n,m,tl;scanf("%d%d%d%d%d%d",&n,&m,&S,&T,&tl,&ad);
		
		for(int i=1;i<=n;i++) scanf("%d%d",&bh[i],&lh[i]);
		
		for(int i=1;i<=m;i++)
		{
			int x,y,l;scanf("%d%d%d",&x,&y,&l);
			G.add_edge(x,y,l),G.add_edge(y,x,l);
		}
		
		SPFA();
		
		if( dis[T]<=tl ) printf("%d",dis[T]);
		else printf("wtnap wa kotori no oyatsu desu!");
		
		return;
	}
	
}SOL;

int main()
{
	SOL.solve();return 0;
}

T2

这个有点意思。

最短距离不难求,但是怎么求方案数呢?

if( dis[s][i]+dis[i][t]==dis[s][t] ) ans[s][t]+=ans[s][i]*ans[i][t];

上面的式子不难理解,但是实际题目中的写法必须是下面这样:

for(int i=1;i<=n;i++)
    for(int s=1;s<=n;s++)
        for(int t=1;t<=n;t++)
            if( s!=i and i!=t and s!=t )
            {						
                if( dis[s][i]+dis[i][t]<dis[s][t] )
                {
                    dis[s][t]=dis[s][i]+dis[i][t];
                    ans[s][t]=ans[s][i]*ans[i][t];
                }
                else if( dis[s][i]+dis[i][t]==dis[s][t] ) ans[s][t]+=ans[s][i]*ans[i][t];
            }

也就是说,我们要边求最短路边统计方案数

为什么要这样呢?难道我不可以求完最短路再求方案数吗?

不可以

上面的例子,算完最短路之后,就会有两种“可行”的方案:

  • (1-2) (2-3-4)
  • (1-2-3) (3-4)

问题在于:对于有多个节点的最短路径,我们要找到表示它的唯一方法

这也就是为什么要在求最短路的时候同时求方案数。

假设现在首先以\(2\)为中继点,那么我们就只能使\(1\to 3\)的方案数等于\(1\),至于\(2\to 4\)我们要不已经维护过了,要不就是维护不了的(因为不能把路径分解为\(2\to 2\)\(2\to 4\),图上没有自环,而且当前\(2\to 4\)还没有被维护到)

然后,再枚举到以\(3\)为中继点的时候,就可以用\(1\to 3\)\(3\to 4\)来表示\(1\to 4\)这条路径了。

(不用担心重复,重复的话意味着\(1\to 2\)\(2\to 4\)也被统计了,但是这里以\(2\)为中继点的时候,我们还没有维护\(2\to 4\)啊,而且维护完\(2\to 4\)的时候我们也不会再去枚举回\(2\)了,所以不会重复)

扯了这么多,就是为了说明:要在求最短路的同时求方案数

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int maxn=102,maxm=9002;

struct Solution
{	
	int dis[maxn][maxn];
	long long ans[maxn][maxn];
	
	void solve()
	{
		memset(dis,0x3f,sizeof(dis));
		int n,m;scanf("%d%d",&n,&m);
	
		for(int i=1;i<=m;i++)
		{
			int x,y,l;scanf("%d%d%d",&x,&y,&l);
			
			dis[x][y]=l,ans[x][y]=1;
			dis[y][x]=l,ans[y][x]=1;
		}
		
		for(int i=1;i<=n;i++)
			for(int s=1;s<=n;s++)
				for(int t=1;t<=n;t++)
					if( s!=i and i!=t and s!=t )
					{						
						if( dis[s][i]+dis[i][t]<dis[s][t] )
						{
							dis[s][t]=dis[s][i]+dis[i][t];
							ans[s][t]=ans[s][i]*ans[i][t];
						}
						else if( dis[s][i]+dis[i][t]==dis[s][t] ) ans[s][t]+=ans[s][i]*ans[i][t];
					}
		
		for(int i=1;i<=n;i++)
		{
			double tmp=0.0;
			
			for(int s=1;s<=n;s++)
			{
				for(int t=1;t<=n;t++)
				{
					if( s==i or i==t or s==t ) continue;
					if( dis[s][i]+dis[i][t]==dis[s][t] ) tmp+=( (double)ans[s][i]*ans[i][t] )/ans[s][t];
				}
			}
			
			printf("%.3lf\n",tmp);
		}
		
		return;
	}
	
}SOL;

int main()
{
	SOL.solve();return 0;
}

T3

其实理解成矩阵就很好理解了:

\(A\)矩阵和\(B\)矩阵分别表示经过\(x\)条边和\(y\)条边的矩阵,那么\(C=A\times B\)就是经过\(x+y\)条边的矩阵。

JZ res;

for(int i=1;i<=idx_cnt;i++)
    for(int s=1;s<=idx_cnt;s++)
        for(int t=1;t<=idx_cnt;t++)
            res.a[s][t]=std::min( res.a[s][t],a[s][i]+op.a[i][t] );

return res;

和普通的\(Floyd\)算法不同的是,在这里,等式两边的矩阵是相互独立的。也就是说,\(C\)矩阵中的\(dis\)不会对\(A\)\(B\)当中的产生影响,所以\(C\)矩阵是切切实实只表示\(x+y\)条边的矩阵。

\(Floyd\)算法中,用\(1\)条边的信息统计完\(2\)条边的信息之后,\(2\)条边的信息又要马上在\(dis\)数组里面用来统计\(3\)条、\(4\)条边的信息,所以不是独立的。

知道具体含义之后就可以矩阵快速幂了:

#include<cstdio>
#include<cstring>
#include<algorithm>

int idx[1002],idx_cnt;

struct JZ
{
	int a[105][105];
	
	JZ(){memset(a,0x3f,sizeof(a));}
	
	JZ operator * (const JZ &op)
	{
		JZ res;
				
		for(int i=1;i<=idx_cnt;i++)
			for(int s=1;s<=idx_cnt;s++)
				for(int t=1;t<=idx_cnt;t++)
					res.a[s][t]=std::min( res.a[s][t],a[s][i]+op.a[i][t] );
		
		return res;
	}
	
	JZ operator *= (const JZ &op)
	{
		*this = (*this)*op;
		return *this;
	}
};

int main()
{
	JZ dis;
	
	int K,m,S,T;scanf("%d%d%d%d",&K,&m,&S,&T);
	
	for(int i=1;i<=m;i++)
	{
		int len,x,y;scanf("%d%d%d",&len,&x,&y);
		
		if( !idx[x] ) idx[x]=++idx_cnt;
		if( !idx[y] ) idx[y]=++idx_cnt;
		
		dis.a[idx[x]][idx[y]]=dis.a[idx[y]][idx[x]]=len;
	}
	
	JZ ans=dis;--K;
	
	while(K)
	{
		if( K&1 ) ans*=dis;
		dis*=dis;K>>=1;
	}
	
	printf("%d",ans.a[idx[S]][idx[T]]);
	return 0;
}

T4

最短路+计算几何是真的神奇。

首先,那些所在直线会穿过被保护节点(把它们分成两部分)的线段是不能建边的(要不就是真的穿过了,要不就是没有穿过,但是方向不对,选了之后会多绕一条线段)

然后就要单向建边

例如,如果对于\(\overrightarrow{AB}\),所有被保护节点都在它的右边(也可以与它共线),那么就可以直接从\(A\)连一条边到\(B\)

其实这样做是为了求有向图最小环(无向图的太难了,偷懒嘛)。

有向图最小环就很简单,直接\(Floyd\)之后,\(dis(i,i)\)就是经过\(i\)点的最小环的长度。

无向图最小环另外讲。

#include<cstdio>
#include<cstring>
#include<algorithm>

const int maxn=502;

struct Solution
{
	struct Point
	{
		int x,y;
		Point(int x=0,int y=0){this->x=x,this->y=y;}
		
		void read(){scanf("%d%d",&x,&y);return;}
	};
	typedef Point Vector;
	
	Vector make_vec(Point s,Point t){return Vector(t.x-s.x,t.y-s.y);}
	int Cross(Vector A,Vector B){return A.x*B.y-A.y*B.x;}
	
	Point P1[maxn],P2[maxn];
	
	int dis[maxn][maxn];
	
	bool solve()
	{
		memset(dis,0x3f,sizeof(dis));
		
		int n1;if( scanf("%d",&n1)==EOF ) return 0;
		for(int i=1;i<=n1;i++) P1[i].read();
		
		int n2;scanf("%d",&n2);
		for(int i=1;i<=n2;i++) P2[i].read();
		
		for(int s=1;s<=n2;s++)
			for(int t=1;t<=n2;t++)
			{
				if( s==t ) continue;
				
				bool flag=1;
				
				for(int k=1;k<=n1;k++)
					if( Cross( make_vec( P2[s],P2[t] ),make_vec( P2[s],P1[k] ) )>0 ){flag=0;break;}
				
				if(flag) dis[s][t]=1;
			}
		
		for(int i=1;i<=n2;i++)
			for(int s=1;s<=n2;s++)
			{
				if( dis[s][i]==0x3f3f3f3f ) continue;
				
				for(int t=1;t<=n2;t++)
					dis[s][t]=std::min( dis[s][t],dis[s][i]+dis[i][t] );
			}
		
		int ans=0x3f3f3f3f;
		for(int i=1;i<=n2;i++) ans=std::min( ans,dis[i][i] );;
		
		if( ans>n2 ) puts("ToT");
		else printf("%d\n",n2-ans);
		
		return 1;
	}

}SOL;

int main()
{
	while( SOL.solve() );return 0;
}
posted @ 2019-08-02 10:39  info___tion  阅读(208)  评论(0编辑  收藏  举报