前言
成功向着成功相反的方向一直走可能也算一种成功吧qwq。
题目
给 \(n\) 个自然数 \(a_i\),以及 \(c_{i,j}\) 表示将 \(a_i\) 和 \(a_j\) 都减一的代价,注意 \(i,j\) 可以相等,此时相当于一个数减两次。
询问把所有数减到 \(0\) 的最小花费。无解输出 \(-1\)。数据范围中的 \(k\) 表示 \(a_i\) 为奇数的个数。
\(1\le n\le 50;k\le 8;c_{i,j}=c_{j,i}\le 100000;a_{i}\le 100.\)
讲解
这么小的数据,这么小的代价,这长得也太网络流了吧!
由于我见题不多,所以我就开始思考怎么让一个代价有两个固定点的流量。
我想的是新建虚点,到虚点的边控制花费,然后虚点连出去的边控制流量,但是我并不会均分出去的流量,遂自闭。
然而正解完全相反,两个点之间的边来控制花费,而点与源汇点的边控制流量!
具体来说,我们将一次 \(c_{i,j}\) 的操作看作从 \(i\) 连向 \(j\) 的一条边,然后将 \(a_i\) 均分为入度和出度,至于奇数点只需暴力枚举入度多 \(1\) 还是出度多 \(1\),也就 \(C_{k}^{k/2}\) 至多 \(C_{8}^{4}\) 种情况。整个过程还可以看成欧拉路。
正确性显然。
时间复杂度不会算,总之就是情况数量乘上费用流的时间。
代码
//12252024832524
#include <bits/stdc++.h>
#define TT template<typename T>
using namespace std;
typedef long long LL;
const int MAXN = 105;
const int INF = 0x3f3f3f3f;
int n,k,S,T,ans = INF;
int a[MAXN],c[MAXN][MAXN];
LL Read()
{
LL x = 0,f = 1;char c = getchar();
while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
TT void Put1(T x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}
int head[MAXN],tot = 1;
struct edge
{
int v,w,c,nxt;
}e[MAXN*MAXN<<2];
void Add_Edge(int u,int v,int w,int c)
{
e[++tot] = edge{v,w,c,head[u]};
head[u] = tot;
}
void Add_Double_Edge(int u,int v,int w,int c)
{
Add_Edge(u,v,w,c);
Add_Edge(v,u,0,-c);
}
bool tag[MAXN],inq[MAXN];
vector<int> odd;
int pre[MAXN][2],flow[MAXN],dis[MAXN];
bool bfs()
{
for(int i = 1;i <= T;++ i) dis[i] = INF,flow[i] = 0;
dis[S] = 0; flow[S] = INF;
queue<int> q; q.push(S);
while(!q.empty())
{
int x = q.front(); q.pop(); inq[x] = 0;
for(int i = head[x],v; i ;i = e[i].nxt)
if(e[i].w > 0 && dis[x] + e[i].c < dis[v = e[i].v])
{
dis[v] = dis[x] + e[i].c;
flow[v] = Min(flow[x],e[i].w);
pre[v][0] = x;
pre[v][1] = i;
if(!inq[v]) inq[v] = 1,q.push(v);
}
}
return dis[T] < INF;
}
int MCMF()
{
int ret = 0;
while(bfs())
{
ret += flow[T] * dis[T];
for(int now = T;now ^ S;now = pre[now][0])
{
e[pre[now][1]].w -= flow[T];
e[pre[now][1]^1].w += flow[T];
}
}
return ret;
}
void solve()
{
tot = 1;
for(int i = 1;i <= T;++ i) head[i] = 0;
for(int i = 1;i <= n;++ i)
if(a[i] & 1)
{
if(tag[i]) Add_Double_Edge(S,i,(a[i]>>1)+1,0),Add_Double_Edge(i+n,T,a[i]>>1,0);
else Add_Double_Edge(S,i,a[i]>>1,0),Add_Double_Edge(i+n,T,(a[i]>>1)+1,0);
}
else Add_Double_Edge(S,i,a[i]>>1,0),Add_Double_Edge(i+n,T,a[i]>>1,0);
for(int i = 1;i <= n;++ i)
for(int j = 1;j <= n;++ j)
Add_Double_Edge(i,j+n,INF,c[i][j]);
ans = Min(ans,MCMF());
}
void dfs(int x,int les)
{
if(x == k)
{
solve();
return;
}
if(k-x-1 >= les) tag[odd[x]] = 0,dfs(x+1,les);
if(les) tag[odd[x]] = 1,dfs(x+1,les-1);
}
int main()
{
// freopen("match.in","r",stdin);
// freopen("match.out","w",stdout);
n = Read();
for(int i = 1;i <= n;++ i)
if((a[i] = Read()) & 1)
odd.emplace_back(i);
for(int i = 1;i <= n;++ i)
for(int j = 1;j <= n;++ j)
c[i][j] = Read();
if((k = odd.size()) & 1) {Put(-1,'\n');return 0;}
S = n<<1|1; T = S+1; dfs(0,k>>1);
Put(ans,'\n');
return 0;
}