CF536D Tavas in Kansas
传送门
题意:两个人博弈 起始点S和T 每次可以选择一个dis 然后覆盖所有的离自己的起始点距离<=dis且没有覆盖的点 然后这些点就被覆盖过了 两个人各自想最大化分差 求最后谁赢以及分差 点数2000边数100000
算法:dp
首先从S,T各自跑一遍最短路把它们作为这个点的横纵坐标,然后分别离散化一下。
我们的问题就转化成了每次第一个人选择几行第二个人选择几列 都要从坐标小的开始取。
然后我们可以DP啦
f[0/1][x][y]表示现在是第一个人/第二个人取 取完了x行 y列
对于第一个人
如果这一行没有点的话那么他一定是跟上次连着取的所以f[0][x][y]=f[0][x-1][y]
不然的话就是两种转移 f[0][x][y]=max(f[0][x-1][y],f[1][x-1][y])+s s表示这一次选的
第二个人只需要改一下0/1和+/-即可
但是对于博弈问题我们是无法确认最终状态的所以我们把状态倒过来做
就是f[0/1][x][y]表示取了[x,n]行[y,m]列的结果 然后由于第一个人先手所以我们就可以确定下来最后的结果是f[0][1][1]
预处理一下行列前缀和即可。
注意取的矩形的位置。
附代码。(细节比较多注意处理+1/-1)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<map>
#define inf 20021225
#define ll long long
using namespace std;
struct edge{int to,lt;ll v;}e[200100];
struct node
{
int x; ll dis;
node(){}
node(int _x,ll _dis){x=_x,dis=_dis;}
};
struct point{int x,y;ll v,dx,dy;}p[2100];
bool operator <(node a,node b){return a.dis>b.dis;}
priority_queue<node> que;bool vis[2100];
ll f[2][2100][2100];int in[2100],cnt,n,m;
map<ll,int> mx,my;int cnx,cny;ll dis[2100];
void add(int x,int y,ll v)
{
e[++cnt].to=y;e[cnt].lt=in[x];e[cnt].v=v;in[x]=cnt;
e[++cnt].to=x;e[cnt].lt=in[y];e[cnt].v=v;in[y]=cnt;
}
void dij(int s)
{
memset(dis,63,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[s]=0;que.push(node(s,dis[s]));
while(!que.empty())
{
node cur=que.top();que.pop();
if(vis[cur.x]) continue;
vis[cur.x]=1;ll tmp=cur.dis;
for(int i=in[cur.x];i;i=e[i].lt)
{
int y=e[i].to;
if(dis[y]>tmp+e[i].v)
dis[y]=tmp+e[i].v,que.push(node(y,dis[y]));
}
}
}
ll mp[2100][2100],sz[2100][2100];
void sum()
{
for(int i=1;i<=cnx+1;i++)
for(int j=1;j<=cny+1;j++)
{
mp[i][j]=mp[i][j]+mp[i-1][j]+mp[i][j-1]-mp[i-1][j-1];
sz[i][j]=sz[i][j]+sz[i-1][j]+sz[i][j-1]-sz[i-1][j-1];
}
}
ll get(int x1,int y1,int x2,int y2)
{
return mp[x2][y2]-mp[x1-1][y2]-mp[x2][y1-1]+mp[x1-1][y1-1];
}
ll size(int x1,int y1,int x2,int y2)
{
if(y2<y1||x2<x1) return 0;
return sz[x2][y2]-sz[x1-1][y2]-sz[x2][y1-1]+sz[x1-1][y1-1];
}
int main()
{
int s,t,x,y;ll v;
scanf("%d%d",&n,&m);
scanf("%d%d",&s,&t);
for(int i=1;i<=n;i++) scanf("%I64d",&p[i].v);
for(int i=1;i<=m;i++)
scanf("%d%d%I64d",&x,&y,&v),add(x,y,v);
//scanf("%d",&flag);
dij(s);
for(int i=1;i<=n;i++) p[i].dx=dis[i];
sort(dis+1,dis+n+1);
for(int i=1;i<=n;i++)
if(!mx[dis[i]]) mx[dis[i]]=++cnx;
for(int i=1;i<=n;i++) p[i].x=mx[p[i].dx];
dij(t);
for(int i=1;i<=n;i++) p[i].dy=dis[i];
sort(dis+1,dis+n+1);
for(int i=1;i<=n;i++)
if(!my[dis[i]]) my[dis[i]]=++cny;
for(int i=1;i<=n;i++) p[i].y=my[p[i].dy],mp[p[i].x][p[i].y]+=p[i].v,sz[p[i].x][p[i].y]++;
sum();
for(int i=cnx+1;i;i--)
{
for(int j=cny+1;j;j--)
{
if(size(i,j,i,cny))
f[0][i][j]=max(f[0][i+1][j],f[1][i+1][j])+get(i,j,i,cny);
else f[0][i][j]=f[0][i+1][j];
if(size(i,j,cnx,j))
f[1][i][j]=min(f[0][i][j+1],f[1][i][j+1])-get(i,j,cnx,j);
else f[1][i][j]=f[1][i][j+1];
//printf("%d %d:*%d %d %d\n",i,j,size(i,1,i,j-1),f[0][i][j],f[1][i][j]);
}
}
if(f[0][1][1]>0) printf("Break a heart\n");
if(f[0][1][1]==0) printf("Flowers\n");
if(f[0][1][1]<0) printf("Cry\n");
return 0;
}
注意博弈论相关的DP一般状态都要倒着取。这样可以确定最后的先手。