CodeForces 536D - Tavas in Kansas

洛谷题目页面传送门 & CodeForces题目页面传送门

A和B在一张无向连通图\(G=(V,E),|V|=n,|E|=m\)上玩一个游戏,节点\(i\)有一个权值\(v_i\)。A、B分别站在节点\(a,b\),轮流操作,每次操作选择一个数\(x\),然后选所有到自己所在地最短距离\(\le x\)且没有被选过的点,并把它们的权值累加到自己的分数中,且要满足至少选了\(1\)个点。直到当前玩家无法操作,游戏结束。A为先手,A、B均希望自己的得分更高且按最优策略操作。设A、B的最终得分分别为\(sco_a,sco_b\),则输出\(\begin{cases}\texttt{Cry}&sco_a<sco_b\\\texttt{Flowers}&sco_a=sco_b\\\texttt{Break a heart}&sco_a>sco_b\end{cases}\)

\(n\in\left[2,2\times10^3\right],m\in\left[n-1,10^5\right],a\ne b,v_i\in\left[-10^9,10^9\right]\)

首先,跑\(2\)遍Dijkstra(由于\(\mathrm O\!\left(n^2\right)\)可过,所以不需要堆优化)是肯定的,算出每个节点\(i\)\(a,b\)分别的最短路径,记为\(dis1_i,dis2_i\)

然后不难发现,以到\(a\)的最短距离来说,不可能存在\(dis1_i<dis1_j\)使得\(j\)被A选了而\(i\)没被选(道理很简单),到\(b\)亦然。那么我们可以很简单地描述出游戏中任意一个可能的局面:有且仅有所有满足\(dis1_x\le i\)\(dis2_x\le j\)\(x\)被选,记为\(S(i,j)\)。考虑将\(dis1,dis2\)离散化(因为我们只需要比较相对大小,且\(dis1,dis2\)的比较是互相独立的,所以不用保留原数值),这样显然\(\forall S(i,j),i,j\le n\),我们就可以把每个局面装到DP数组里,使得状态数在\(\mathrm O(n^2)\)级。

\(dp1_{i,j},dp2_{i,j}\)分别表示若初始局面为局面\(S_{i,j}\),第一步该A、B操作,A得的分数。考虑枚举第一步该操作的玩家新选了那些节点(这些节点不能已经被另一个玩家选过),即将\(i/j\)增加到了多少,然后转移到下一个局面。状态转移方程:

\[\begin{cases}dp1_{i,j}=\max\limits_{\sum\limits_{o=i+1}^k\sum\limits_{dis1_p=o}[dis2_p>j]>0}\left\{dp2_{k,j}+\sum\limits_{o=i+1}^k\sum\limits_{dis1_p=o}[dis2_p>j]v_p\right\}\\dp2_{i,j}=\min\limits_{\sum\limits_{o=j+1}^k\sum\limits_{dis2_p=o}[dis1_p>i]>0}\left\{dp1_{i,k}\right\}\end{cases} \]

这样\(1\)次转移显然是\(\mathrm O(n)\)的,总复杂度\(\mathrm O\!\left(n^3\right)\),会炸。注意到方程里面有区间和,于是预处理前缀和\(Cnt1_{i,j}=\sum\limits_{k=1}^i\sum\limits_{dis1_o=k}[dis2_o>j],Sum1_{i,j}=\sum\limits_{k=1}^i\sum\limits_{dis1_o=k}[dis2_o>j]v_o,Cnt2_{i,j}=\sum\limits_{k=1}^i\sum\limits_{dis2_o=k}[dis1_o>j]\),方程变为了:

\[\begin{cases}dp1_{i,j}=\max\limits_{Cnt1_{k,j}>Cnt1_{i,j}}\left\{dp2_{k,j}+Sum1_{k,j}-Sum1_{i,j}\right\}=\max\limits_{Cnt1_{k,j}>Cnt1_{i,j}}\left\{dp2_{k,j}+Sum1_{k,j}\right\}-Sum1_{i,j}\\dp2_{i,j}=\min\limits_{Cnt2_{i,k}>Cnt2_{i,j}}\left\{dp1_{i,k}\right\}\end{cases} \]

首先明确一点,DP时是按\(i,j\)的倒序递推的(因为只有\(i'\ge i,j'\ge j\)\(S(i,j)\)才可能转移到\(S(i',j')\))。不难发现,\(dp1\)的转移方程中,决策变量\(k\)的取值范围一定是一个区间,设它为\([k_{\min},k_{\max}]\),则若\(j\)固定,随着\(i\)的下降,\(k_{\max}=|dis1|\)恒成立,\(k_{\min}\)单调不升。这样一来,就可以two-pointers了,我们可以维护\(now1,mx1\)数组,\(now1_o,mx1_o\)表示当\(j=o\)时当前的\(k_{\min}\)、所有决策中最大的\(dp2_{k,j}+Sum1_{k,j}\)\(i\)每减少\(1\)时,就将\(now1_j\)减少若干并更新\(mx1_j\)。对\(dp2\)的处理类似,维护\(now2,mn2\)

最终比较\(dp1_{0,0}\)\(\sum\limits_{i=1}^nv_i-dp1_{0,0}\)的大小即可。对了还有边界,就是无法转移到任何局面的DP值为\(0\)

用了two-pointers,计算每个DP值的时间复杂度均摊\(\mathrm O(1)\),再加上最先的Dijkstra,总复杂度就是\(\mathrm O\!\left(n^2\right)\)了。

下面贴代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long//防爆int 
#define mp make_pair
#define X first
#define Y second
#define pb push_back
const int inf=0x3f3f3f3f3f3f3f3f; 
const int N=2000;
int n/*点数*/,m/*边数*/,a/*A所在节点*/,b/*B所在节点*/;
int v[N+1];//点权 
vector<pair<int,int> > nei[N+1];//邻接表 
int dis1[N+1],dis2[N+1];//到a、b的最短距离 
bool vis[N+1]; 
void dijkstra(int x,int dis[]){//Dijkstra求dis1,dis2 
	for(int i=1;i<=n;i++)dis[i]=inf;
	dis[x]=0;
	memset(vis,0,sizeof(vis));
	while(true){
		pair<int,int> mn(inf,0);
		for(int j=1;j<=n;j++)if(!vis[j])mn=min(mn,mp(dis[j],j));
		int y=mn.Y;
		if(!y)break;
		vis[y]=true;
		for(int j=0;j<nei[y].size();j++){
			int z=nei[y][j].X,len=nei[y][j].Y;
			dis[z]=min(dis[z],dis[y]+len);
		}
	}
//	printf("dis=");for(int i=1;i<=n;i++)cout<<dis[i]<<" ";puts("");
}
vector<int> nums1,nums2;//离散化辅助数组 
vector<int> hav1[N+1],hav2[N+1];//hav1[i]中存储所有满足dis1[x]=i(离散化之后)的节点x,hav2类似 
void discrete(vector<int> &nums,int dis[],vector<int> hav[]){//离散化 
	nums.pb(-1);//比任何距离都小 
	for(int i=1;i<=n;i++)nums.pb(dis[i]);
	sort(nums.begin(),nums.end());
	nums.resize(unique(nums.begin(),nums.end())-nums.begin());
	for(int i=1;i<=n;i++)hav[dis[i]=lower_bound(nums.begin(),nums.end(),dis[i])-nums.begin()].pb(i);
}
int Cnt1[N+1][N+1],Sum1[N+1][N+1],Cnt2[N+1][N+1];//前缀和 
int now1[N+1],now2[N+1],mx1[N+1],mn2[N+1];//优化DP的two-pointers数组 
int dp1[N+1][N+1],dp2[N+1][N+1];//DP数组 
signed main(){
	cin>>n>>m>>a>>b;
	int sum=0;//所有点权和 
	for(int i=1;i<=n;i++)cin>>v[i],sum+=v[i];
	while(m--){
		int x,y,z;
		cin>>x>>y>>z;
		nei[x].pb(mp(y,z));nei[y].pb(mp(x,z));
	}
	dijkstra(a,dis1);dijkstra(b,dis2);//求出距离数组 
	discrete(nums1,dis1,hav1);discrete(nums2,dis2,hav2);//离散化距离数组 
	for(int i=1;i<nums1.size();i++)for(int j=0;j<nums2.size();j++){//预处理Cnt1,Sum1 
		Cnt1[i][j]=Cnt1[i-1][j];Sum1[i][j]=Sum1[i-1][j];
		for(int k=0;k<hav1[i].size();k++){
			bool ok=dis2[hav1[i][k]]>j;
			Cnt1[i][j]+=ok;Sum1[i][j]+=ok*v[hav1[i][k]];
		}
	}
	for(int i=1;i<nums2.size();i++)for(int j=0;j<nums1.size();j++){//预处理Cnt2 
		Cnt2[i][j]=Cnt2[i-1][j];
		for(int k=0;k<hav2[i].size();k++)Cnt2[i][j]+=dis1[hav2[i][k]]>j;
	}
	for(int i=0;i<=n;i++)now1[i]=nums1.size(),now2[i]=nums2.size(),mx1[i]=-inf,mn2[i]=inf;//赋初始值 
	for(int i=nums1.size()-1;~i;i--)for(int j=nums2.size()-1;~j;j--){//按i,j倒序DP 
		while(now1[j]&&Cnt1[now1[j]-1][j]>Cnt1[i][j])now1[j]--,mx1[j]=max(mx1[j],dp2[now1[j]][j]+Sum1[now1[j]][j]);//two-pointers 
		dp1[i][j]=mx1[j]>-inf?/*能转移?*/mx1[j]-Sum1[i][j]/*按方程来*/:0/*为0*/;
		while(now2[i]&&Cnt2[now2[i]-1][i]>Cnt2[j][i])now2[i]--,mn2[i]=min(mn2[i],dp1[i][now2[i]]);//同上2行 
		dp2[i][j]=mn2[i]<inf?mn2[i]:0;//同上2行 
//		printf("dp1[%lld][%lld]=%lld dp2[%lld][%lld]=%lld\n",i,j,dp1[i][j],i,j,dp2[i][j]);
	}
	int ans=dp1[0][0];//A的最优得分 
	puts(ans<sum-ans?"Cry":ans==sum-ans?"Flowers":"Break a heart");
	return 0;
}
posted @ 2019-12-09 01:44  ycx060617  阅读(201)  评论(4编辑  收藏  举报