题目链接:点击这里
题意: 给出一个序列, 问子序列的最大收益. 收益=价值-花费, 花费是子序列里面某个数字的个数的函数, 收益是任意两个下标对应的函数.
题解建模:
首先将点分为3类
第一类:Pij 表示第i个点和第j个点组合的点,那么Pij的权值等于 w[i][j]+w[j][i] (表示得分)
第二类:原串中的n个点每个点拆出一个点,第i个点权值为 –a[s[i]] (表示要花费)
第三类:对于10种字符拆出10个点,每个点的权值为 −(b[x]−a[x])
那么我们可以得到一个关系图 ,对于第一类中的点 Pij ,如果想要选择 Pij ,你就必须要选中第二类中的点i和j,对于第二类中的点如果你想选中第i个点,其对应的字符 s[i] ,那么就必须选中第三类中 s[i] 对应的点,因为每个种类的点第一次选中时花费是b[s[i]],而第二类中花费都是 a[s[i]] ,一定要补上 b[s[i]]−a[s[i]] ,而且只需要补上一次。
得到上面的关系图后然后就是普通的最大权闭合子图问题,直接求解即可。
#include <bits/stdc++.h>
using namespace std;
#define maxn 10005
#define maxm maxn*10
#define INF 111111111111111
#define type long long
char str[105];
int n;
int s, t;
struct Edge
{
int from, to,next;
type cap,flow;
void get(int u,int a,int b,type c,type d)
{
from = u; to = a; next = b; cap = c; flow = d;
}
}edge[maxm];
int tol;
int head[maxn];
int gap[maxn],dep[maxn],pre[maxn],cur[maxn];
void init()
{
tol=0;
memset(head,-1,sizeof(head));
}
void add_edge(int u,int v,type w,type rw=0)
{ //cout << u << " " << v << " " << w << endl;
edge[tol].get(u, v,head[u],w,0);head[u]=tol++;
edge[tol].get(v, u,head[v],rw,0);head[v]=tol++;
}
type sap(int start,int end,int N)
{
memset(gap,0,sizeof(gap));
memset(dep,0,sizeof(dep));
memcpy(cur,head,sizeof(head));
int u=start;
pre[u]=-1;
gap[0]=N;
type ans=0;
while(dep[start]<N)
{
if(u==end)
{
type Min=INF;
for(int i=pre[u];i!=-1;i=pre[edge[i^1].to])
if(Min>edge[i].cap-edge[i].flow)
Min=edge[i].cap-edge[i].flow;
for(int i=pre[u];i!=-1;i=pre[edge[i^1].to])
{
edge[i].flow+=Min;
edge[i^1].flow-=Min;
}
u = start;
ans+=Min;
continue;
}
bool flag=false;
int v;
for(int i=cur[u];i !=-1;i=edge[i].next)
{
v=edge[i].to;
if(edge[i].cap-edge[i].flow&&dep[v]+1==dep[u])
{
flag=true;
cur[u]=pre[v]=i;
break;
}
}
if(flag)
{
u=v;
continue;
}
int Min=N;
for(int i=head[u];i!=-1;i=edge[i].next)
if(edge[i].cap-edge[i].flow&&dep[edge[i].to]<Min)
{
Min=dep[edge[i].to];
cur[u]=i;
}
gap[dep[u]]--;
if(!gap[dep[u]]) return ans;
dep[u]=Min+1;
gap[dep[u]]++;
if(u!=start) u=edge[pre[u]^1].to;
}
return ans;
}
long long a[15], b[15], w[105][105];
void solve () {
s = 0;
int node = n + 10;
long long tot = 0;
for (int i = 1; i <= n; i++) {
for (int j = i+1; j <= n; j++) {
node++;
add_edge (s, node, w[i][j]+w[j][i], 0);
add_edge (node, i, INF, 0);
add_edge (node, j, INF, 0);
tot += w[i][j]+w[j][i];
}
}
t = ++node;
for (int i = 1; i <= n; i++) {
add_edge (i, str[i]+n+1, INF, 0);
add_edge (i, t, a[str[i]+1], 0);
}
for (int i = 1; i <= 10; i++) {
add_edge (i+n, t, b[i]-a[i], 0);
}
long long ans = sap (s, t, node);
printf ("%lld\n", tot-ans);
}
int main () {
int t, kase = 0;
scanf ("%d", &t);
while (t--) {
scanf ("%d", &n);
if (n == 0) {
for (int i = 1; i <= 10; i++) {
int u , v;
scanf ("%d%d", &u, &v);
}
printf ("Case #%d: 0\n", ++kase);
continue;
}
scanf ("%s", str+1);
init ();
for (int i = 1; i <= n; i++) str[i] -= '0';
for (int i = 1; i <= 10; i++) {
scanf ("%lld%lld", &a[i], &b[i]);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
scanf ("%lld", &w[i][j]);
}
}
printf ("Case #%d: ", ++kase);
solve ();
}
return 0;
}
/*
10
10
9874563210
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
*/