• CodeForces 536D


    洛谷题目页面传送门 & 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$,则输出$egin exttt&sco_a<sco_b\texttt&sco_a=sco_b\texttt&sco_a>sco_bend$。

    (ninleft[2,2 imes10^3 ight],minleft[n-1,10^5 ight],a e b,v_iinleft[-10^9,10^9 ight])

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

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

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

    (egin{cases}dp1_{i,j}=maxlimits_{sumlimits_{o=i+1}^ksumlimits_{dis1_p=o}[dis2_p>j]>0}left{dp2_{k,j}+sumlimits_{o=i+1}^ksumlimits_{dis1_p=o}[dis2_p>j]v_p ight}\dp2_{i,j}=minlimits_{sumlimits_{o=j+1}^ksumlimits_{dis2_p=o}[dis1_p>i]>0}left{dp1_{i,k} ight}end{cases})

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

    (egin{cases}dp1_{i,j}=maxlimits_{Cnt1_{k,j}>Cnt1_{i,j}}left{dp2_{k,j}+Sum1_{k,j}-Sum1_{i,j} ight}=maxlimits_{Cnt1_{k,j}>Cnt1_{i,j}}left{dp2_{k,j}+Sum1_{k,j} ight}-Sum1_{i,j}\dp2_{i,j}=minlimits_{Cnt2_{i,k}>Cnt2_{i,j}}left{dp1_{i,k} ight}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}(与)sumlimits_^nv_i-dp1_{0,0}$的大小即可。对了还有边界,就是无法转移到任何局面的DP值为$0$。

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

    下面贴代码:

    #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
    ",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;
    }
    
  • 相关阅读:
    JZOJ8月10日提高组T2 Fix
    【GDOI2007】JZOJ2020年8月10日提高组T1 夏娜的菠萝包
    JZOJ8月10日提高组反思
    【NOIP2012模拟8.7】JZOJ2020年8月8日提高组T1 奶牛编号
    JZOJ8月8日提高组反思
    【佛山市选2013】JZOJ2020年8月7日T4 排列
    【佛山市选2013】JZOJ2020年8月7日提高组T3 海明距离
    【佛山市选2013】JZOJ2020年8月7日提高组T2 树环转换
    【佛山市选2013】JZOJ2020年8月7日提高组T1 回文子序列
    写给在工厂上班的大学同学们
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/CodeForces-536D.html
Copyright © 2020-2023  润新知