把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【CF536D】Tavas in Kansas(博弈+动态规划)

点此看题面

  • 给定一张\(n\)个点\(m\)条边的无向图,每个点有一个点权(可能为负),每条边有一个长度。
  • 两名玩家分别处于\(A,B\)两点,轮流操作,每次指定一个距离\(x\),获得到他距离不超过\(x\)的所有未被获得过的点权(至少要包含一个未被获得过的点),直至所有点都被获得过。
  • 最终获得点权大的人胜,判断谁有必胜策略或平局。
  • \(n\le2\times10^3,m\le10^5\)

最短路+离散化

首先我们肯定需要先预处理出\(A,B\)两点到所有点的距离,直接\(Dijkstra\)即可。

注意距离可能很大,但那些没有点的距离显然是无用的,因此完全可以对\(A,B\)两点到所有点的距离分别离散化,使得距离全在\([1,n]\)范围内。

博弈论+动态规划

这种问题常见的套路应该是维护先手权值与后手权值之差,那么先手就是要最大化这个差值,后手就是要最小化这个差值。

由于一个人选的距离只能越来越大(因为如果距离小了必然所有点都已被获得过),所以有用的信息只有两人最后选择的距离。

这样一来就可以建出一个二维平面,每个点根据到\(A,B\)两点的距离表示为二维平面上一个点\((dA_i,dB_i)\),点权为\(a_i\),并给点数和点权分别做一个二维前缀和命名为\(c_{i,j}\)\(s_{i,j}\)

于是设\(f_{0/1,i,j}\)表示现在轮到先手/后手操作,两人上次操作分别选择了距离\(i,j\)

根据博弈论的经典套路,我们从最终状态向初始状态\(f_{0,0,0}\)转移,转移式为:(对于最终状态直接赋值为\(0\)

\[f_{0,i,j}=\max\{f_{1,i',j}+s_{i+1,j+1}-s_{i'+1,j+1}|c_{i+1,j+1}\not=c_{i'+1,j+1}\}\\ f_{1,i,j}=\min\{f_{0,i,j'}-s_{i+1,j+1}+s_{i+1,j'+1}|c_{i+1,j+1}\not=c_{i+1,j'+1}\} \]

式子中的\(s_{i+1,j+1}\)可以直接提出,然后就会发现剩余项中其实并没有同时与\(i,i'\)或同时与\(j,j'\)有关的项。

又由于同一列和同一行的\(c_{i,j}\)都具有单调性,我们可以对每一列和每一行分别开一个指针维护可转移的最小位置,并分别维护最大值/最小值便于转移。(其实就是特殊的双指针)

最后只要判断\(f_{0,0,0}\)的符号即可。

代码:\(O(n^2+mlogn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 2000
#define M 100000
#define LL long long
#define Pr pair<LL,int>
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define Gmin(x,y) (x>(y)&&(x=(y)))
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].v=z)
using namespace std;
int n,m,A,B,a[N+5],ee,lnk[N+5];struct edge {int to,nxt,v;}e[2*M+5];
int dA[N+5],dB[N+5],c[N+5][N+5];LL s[N+5][N+5];
namespace Dij//最短路
{
	int dc,vis[N+5];LL dis[N+5],dv[N+5];priority_queue<Pr,vector<Pr>,greater<Pr> > q;
	I void Solve(CI s,int* d)
	{
		RI i,k;for(i=1;i<=n;++i) dis[i]=1e18,vis[i]=0;q.push(make_pair(dis[s]=0,s));
		W(!q.empty()) if(k=q.top().second,q.pop(),!vis[k]) for(vis[k]=1,i=lnk[k];i;i=e[i].nxt)//Dijkstra
			dis[k]+e[i].v<dis[e[i].to]&&(q.push(make_pair(dis[e[i].to]=dis[k]+e[i].v,e[i].to)),0);
		for(i=1;i<=n;++i) dv[i]=dis[i];sort(dv+1,dv+n+1),dc=unique(dv+1,dv+n+1)-dv-1;
		for(i=1;i<=n;++i) d[i]=lower_bound(dv+1,dv+dc+1,dis[i])-dv;//离散化
	}
}
namespace DP//动态规划
{
	int p[N+5],q[N+5];LL u[N+5],v[N+5],f[2][N+5][N+5];
	I void Solve()
	{
		RI i,j;for(i=0;i<=n;++i) p[i]=q[i]=n,u[i]=-1e18,v[i]=1e18;
		for(i=n;~i;--i) for(j=n;~j;--j)//从最终状态向最初状态
		{
			W(c[p[j]+1][j+1]^c[i+1][j+1]) Gmax(u[j],f[1][p[j]][j]-s[p[j]+1][j+1]),--p[j];//维护列的指针和最大值
			W(c[i+1][q[i]+1]^c[i+1][j+1]) Gmin(v[i],f[0][i][q[i]]+s[i+1][q[i]+1]),--q[i];//维护行的指针和最小值
			f[0][i][j]=p[j]^n?u[j]+s[i+1][j+1]:0,f[1][i][j]=q[i]^n?v[i]-s[i+1][j+1]:0;//DP转移,最终状态为0
		}
		puts(f[0][0][0]?(f[0][0][0]>0?"Break a heart":"Cry"):"Flowers");//判断符号
	}
}
int main()
{
	RI i,j,x,y,z;for(scanf("%d%d%d%d",&n,&m,&A,&B),i=1;i<=n;++i) scanf("%d",a+i);
	for(i=1;i<=m;++i) scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z);
	for(Dij::Solve(A,dA),Dij::Solve(B,dB),i=1;i<=n;++i) ++c[dA[i]][dB[i]],s[dA[i]][dB[i]]+=a[i];//根据距离表示到二维平面上
	for(i=n;~i;--i) for(j=n;~j;--j) c[i][j]+=c[i+1][j],s[i][j]+=s[i+1][j];//二维前缀和(包括下一行)
	for(i=n;~i;--i) for(j=n;~j;--j) c[i][j]+=c[i][j+1],s[i][j]+=s[i][j+1];return DP::Solve(),0; 
}
posted @ 2021-04-02 18:32  TheLostWeak  阅读(59)  评论(0编辑  收藏  举报