CF321B Ciel and Duel
\(Ciel\) 某天興致勃勃地找 \(Juro\) 玩起了一種卡牌遊戲。每張卡牌有型別(攻擊或防禦)和力量值兩個資訊。
\(Juro\) 有 \(n\) 張卡牌,\(Ciel\) 有 \(m\) 張卡牌。已知 \(Ciel\) 的卡牌全是攻擊型的。
遊戲的每一輪都由 \(Ciel\) 進行操作,首先從自己手上選擇一張沒有使用過的卡牌X。
如果 \(Juro\) 手上沒有卡牌,受到的傷害為 X 的力量值,否則 \(Ciel\) 要從 \(Juro\) 的手上選擇一張卡牌 Y
。
若 Y 是攻擊型(當 X 的力量值不小於 Y 的力量值時才可選擇),此輪結束後 Y 消失,\(Juro\) 受到的傷害為 X 的力量值與 Y 的力量值的差;若 Y 是防禦型(當 X 的力量值大於 Y 的力量值時才可選擇),此輪結束後 Y 消失,\(Juro\)
\(Ciel\) 可以隨時結束自己的操作(卡牌不一定要用完)。她想使得 \(Juro\) 受到的總傷害最大。你知道自己要做什麼了嗎?
\(1\le n,m\le100,0\le X,Y\le8000\)
好多做法啊,來一發費用流吧QAQ
對於這個題,我們會發現幾個比較特殊的點:
-
\(Ciel\)可以選擇隨時結束操作。
-
當\(Juro\)沒有牌之後,\(Ciel\)可以把自己剩下的牌全打上去。
第一個操作考慮直接列舉打了幾張牌,多建一個點連向源點限制流量就可以。
重點是第二個操作,可以考慮把\(Juro\)的牌拆成兩個點\(i,i'\)。
-
\(i\to i'\)流量\(1\)
-
\(i\to T'\),流量\(1\),費用\(0\);
用\(j\)表示\(Ciel\)的牌。
-
\(S\to j\),流量\(1\),費用\(0\);
-
\(j\to i(k_i=ATK,X_j\le Y_i)\),流量\(1\),費用\(inf-(X_j-Y_i)\);
-
\(j\to i(k_i=DEF,X_j>Y_i)\),流量\(1\),費用\(inf\);
-
\(j\to T\),流量\(1\),費用\(inf-X_j\)。
這樣子為什麼是對的?
注意那條費用為\(-2inf\)的邊,因為我們跑最小費用最大流,所以我們肯定會優先流完那些邊,才會流\(inf-X_j\)
而我們之前多枚舉了打幾張牌\(x\),所以我們可以很容易通過最小費用還原傷害,最後建個這樣的邊:
- \(SS\to S\),流量\(x\),費用\(0\)。
畫出圖來就是這樣子的:
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
const int N = 100;
const int M = 1e6;
const int inf = 1e7;
const int INF = 2e9;
using namespace std;
struct card
{
int typ,v;
}a[N + 5];
int n,m,b[N + 5],s,ss,t,nxt[M * 2 + 5],head[M + 5],edge_cnt = 1,chi,cost,ans,ress,cur[M + 5],dis[M + 5],vis[M + 5],p[M + 5],q[M + 5];
struct edges
{
int v,w,f;
}edge[M * 2 + 5],e[M * 2 + 5];
char ch[10];
void add_edge(int u,int v,int w,int f)
{
edge[++edge_cnt] = (edges){v,w,f};
nxt[edge_cnt] = head[u];
head[u] = edge_cnt;
}
bool spfa(int s,int t)
{
for (int i = 1;i <= t;i++)
{
cur[i] = head[i];
dis[i] = INF;
p[i] = 0;
vis[i] = 0;
}
int l = 1,r = 0;
dis[s] = 0;
q[++r] = s;
vis[s] = 1;
while (l <= r)
{
int u = q[l++];
vis[u] = 0;
for (int i = head[u];i;i = nxt[i])
{
int v = edge[i].v,w = edge[i].w,f = edge[i].f;
if (dis[u] + f < dis[v] && w)
{
dis[v] = dis[u] + f;
if (!vis[v])
{
vis[v] = 1;
q[++r] = v;
}
}
}
}
return dis[t] != INF;
}
int dfs(int u,int flow)
{
if (u == t)
return flow;
int sm = 0;
p[u] = 1;
for (int &i = cur[u];i;i = nxt[i])
{
int v = edge[i].v,&w = edge[i].w,f = edge[i].f;
if (dis[u] + f == dis[v] && w && !p[v])
{
int res = dfs(v,min(w,flow));
w -= res;
sm += res;
flow -= res;
edge[i ^ 1].w += res;
cost += f * res;
if (!flow)
break;
}
}
return sm;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i = 1;i <= n;i++)
{
scanf("%s",ch + 1);
scanf("%d",&a[i].v);
if (ch[1] == 'A')
a[i].typ = 1;
else
a[i].typ = 2;
}
for (int i = 1;i <= m;i++)
scanf("%d",&b[i]);
s = n * 2 + m + 1,ss = n * 2 + m + 2,t = n * 2 + m + 3;
for (int i = 1;i <= m;i++)
{
add_edge(ss,n * 2 + i,1,0);
add_edge(n * 2 + i,ss,0,0);
add_edge(n * 2 + i,t,1,inf - b[i]);
add_edge(t,n * 2 + i,0,b[i] - inf);
}
for (int i = 1;i <= n;i++)
{
add_edge(i,i + n,1,-2 * inf);
add_edge(i + n,i,0,2 * inf);
add_edge(i + n,t,1,0);
add_edge(t,i + n,0,0);
for (int j = 1;j <= m;j++)
{
if (a[i].typ == 1 && b[j] >= a[i].v)
{
add_edge(n * 2 + j,i,1,inf - (b[j] - a[i].v));
add_edge(i,n * 2 + j,0,b[j] - a[i].v - inf);
}
if (a[i].typ == 2 && b[j] > a[i].v)
{
add_edge(n * 2 + j,i,1,inf);
add_edge(i,n * 2 + j,0,-inf);
}
}
}
add_edge(s,ss,0,0);
chi = edge_cnt;
add_edge(ss,s,0,0);
for (int i = 2;i <= edge_cnt;i++)
e[i] = edge[i];
for (int i = 1;i <= m;i++)
{
e[chi].w = i;
for (int j = 2;j <= edge_cnt;j++)
edge[j] = e[j];
cost = 0;
while (spfa(s,t))
ress += dfs(s,inf);
//cout<<i<<" "<<cost<<" "<<-(inf * min(n,i) - inf * max(i - n,0) + cost)<<endl;
ans = max(ans,-(inf * min(n,i) - inf * max(i - n,0) + cost));
}
cout<<ans<<endl;
return 0;
}