zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    Max Sum Plus Plus HDU
    Monkey and Banana HDU
    Ignatius and the Princess IV HDU
    Extended Traffic LightOJ
    Tram POJ
    Common Subsequence HDU
    最大连续子序列 HDU
    Max Sum HDU
    畅通工程再续
    River Hopscotch POJ
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/CodeForces-536D.html
Copyright © 2011-2022 走看看