【CF536D】Tavas in Kansas
阿新 • • 發佈:2021-10-08
題目
題目連結:https://codeforces.com/problemset/problem/536/D
- 給定一張 \(n\) 個點 \(m\) 條邊的可能有自環和重邊的無向連通圖,每條邊都有一個非負邊權。
- 小 X 和小 Y 在這張圖上玩一個遊戲,在遊戲中,第 \(i\) 個城市有一個權值 \(p_i\)。
- 一開始,小 X 在城市 \(s\) 中,小 Y 在城市 \(t\) 中,兩人各有一個得分,初始為 \(0\),小 X 為先手,然後輪流進行操作。
- 當輪到某一個人時,他必須選擇一個非負整數 \(x\),以選定所有與他所在的城市的最短距離不超過 \(x\) 的還未被選定過的城市,他的得分將會加上這些城市的權值。
- 另外,每個人每次必須能夠至少選定一個城市。
- 當沒有人可以選擇時,遊戲結束,得分高者獲勝。
- 現在請你計算出,在兩人都使用最佳策略的情況下,誰會獲勝(或者判斷為平局)。
- \(n \le 2 \times 10^3\),\(m \le 10^5\),\(|p_i| \le 10^9\)。
思路
首先跑兩次單元最短路徑,求出點 \(x\) 到 \(s\) 和 \(t\) 的距離 \(dis1_x,dis2_x\)。
因為這道題只關心距離的大小關係,所以可以把 \(dis1,dis2\) 分別離散化一下。然後點 \(x\) 就對應到一個 \(n\times n\) 的網格的 \((dis1_x,dis2_x)\)
那麼此時先手就是取頂部的若干行,後手就是取左側的若干列。
記 \(f[0/1][i][j]\) 表示現在到先手/後手取,先手取到第 \(i\) 行,後手取到第 \(j\) 列,此時先手權值減去後手權值的最大/最小值。
直接正著 dp 可能會把對方非最優的情況計算到自己最優的情況中(也就是對方不走這個方案,但是轉移的時候你的最優方案從對方這個不選的方案轉移過來),並且有兩種結束狀態。所以考慮倒過來 dp。
拿 \(f[0][i][j]\) 為例,如果 \((i,j)\) 到 \((i,n)\) 這個子矩陣內至少有一個點,那麼 \[f[0][i][j]=\max(f[0][i+1][j],f[1][i+1][j])+\text{calc}(i,j,i,n) \]
否則
最後只需要看 \(f[0][1][1]\) 的正負性即可。
時間複雜度 \(O(n^2)\)。
程式碼
#include <bits/stdc++.h>
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
const int N=2010,M=200010;
int n,m,s,t,tot,c1,c2,a[N],head[N],cnt[N][N];
ll sum[N][N],dis1[N],dis2[N],b[N],c[N],f[2][N][N];
bool vis[N];
struct edge
{
int next,to,dis;
}e[M];
void add(int from,int to,int dis)
{
e[++tot]=(edge){head[from],to,dis};
head[from]=tot;
}
void dij(int s,ll *dis)
{
memset(vis,0,sizeof(vis));
priority_queue<pair<ll,int> > q;
q.push(mp(0,s)); dis[s]=0;
while (q.size())
{
int u=q.top().se; q.pop();
if (vis[u]) continue;
vis[u]=1;
for (int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
if (dis[v]>dis[u]+e[i].dis)
{
dis[v]=dis[u]+e[i].dis;
q.push(mp(-dis[v],v));
}
}
}
}
int calc1(int x,int y,int xx,int yy)
{
return cnt[xx][yy]-cnt[xx][y-1]-cnt[x-1][yy]+cnt[x-1][y-1];
}
ll calc2(int x,int y,int xx,int yy)
{
return sum[xx][yy]-sum[xx][y-1]-sum[x-1][yy]+sum[x-1][y-1];
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d%d%d",&n,&m,&s,&t);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
for (int i=1,x,y,z;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
add(x,y,z); add(y,x,z);
}
memset(dis1,0x3f3f3f3f,sizeof(dis1));
memset(dis2,0x3f3f3f3f,sizeof(dis2));
dij(s,dis1); dij(t,dis2);
for (int i=1;i<=n;i++)
b[i]=dis1[i],c[i]=dis2[i];
sort(b+1,b+1+n);
c1=unique(b+1,b+1+n)-b-1;
sort(c+1,c+1+n);
c2=unique(c+1,c+1+n)-c-1;
for (int i=1;i<=n;i++)
{
int x=lower_bound(b+1,b+1+c1,dis1[i])-b;
int y=lower_bound(c+1,c+1+c2,dis2[i])-c;
sum[x][y]+=a[i]; cnt[x][y]++;
}
for (int i=1;i<=c1+1;i++)
for (int j=1;j<=c2+1;j++)
{
sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
cnt[i][j]+=cnt[i-1][j]+cnt[i][j-1]-cnt[i-1][j-1];
}
for (int i=c1+1;i>=1;i--)
for (int j=c2+1;j>=1;j--)
{
if (i==c1+1 && j==c2+1) continue;
if (!calc1(i,j,i,c2)) f[0][i][j]=f[0][i+1][j];
else f[0][i][j]=max(f[0][i+1][j],f[1][i+1][j])+calc2(i,j,i,c2);
if (!calc1(i,j,c1,j)) f[1][i][j]=f[1][i][j+1];
else f[1][i][j]=min(f[0][i][j+1],f[1][i][j+1])-calc2(i,j,c1,j);
}
if (!f[0][1][1]) cout<<"Flowers";
if (f[0][1][1]>0) cout<<"Break a heart";
if (f[0][1][1]<0) cout<<"Cry";
return 0;
}