@ljt12138
2017-06-09T11:30:46.000000Z
字数 16497
阅读 968
以前用启发式合并做的,突然脑补出了线段树合并做法,比启发式合并快到不知哪里去了。
#include <bits/stdc++.h>using namespace std;const int MAXN = 100005;int n, m, k;int l[MAXN*20], r[MAXN*20], lc[MAXN*20], rc[MAXN*20], dat[MAXN*20];int top = 0;int build(int opl, int opr, int pos){int nd = ++top;l[nd] = opl, r[nd] = opr, dat[nd] = 1;if (opl < opr) {int mid = (opl+opr)/2;if (pos <= mid) lc[nd] = build(opl, mid, pos);else rc[nd] = build(mid+1, opr, pos);}return nd;}int merge(int a, int b){if (a==0||b==0) return a+b;dat[a] += dat[b];lc[a] = merge(lc[a], lc[b]);rc[a] = merge(rc[a], rc[b]);return a;}int kth(int nd, int k){int l = 1, r = n;while (l < r) {int dt = dat[lc[nd]];if (dt >= k) r = (l+r)/2, nd = lc[nd];else k -= dt, l = (l+r)/2+1, nd = rc[nd];}return l;}int tr[MAXN];int fa[MAXN];inline int findf(int i){ return fa[i]?fa[i]=findf(fa[i]):i; }int atp[MAXN];int main(){scanf("%d%d", &n, &m);for (int i = 1; i <= n; i++) {int d; scanf("%d", &d);atp[d] = i;tr[i] = build(1, n, d);}for (int i = 1; i <= m; i++) {int u, v;scanf("%d%d", &u, &v);u = findf(u), v = findf(v);if (u != v) fa[u] = v, tr[v] = merge(tr[u], tr[v]);}scanf("%d", &k);char str[3];int u, v;for (int i = 1; i <= k; i++) {scanf("%s %d %d", str, &u, &v);if (str[0] == 'B') {u = findf(u), v = findf(v);if (u != v) fa[u] = v, tr[v] = merge(tr[u], tr[v]);} else {u = findf(u);if (dat[tr[u]] < v) puts("-1");else printf("%d\n", atp[kth(tr[u], v)]);}}return 0;}
以前觉得挺神的题...推了推发现还是很水的。
容易发现:
由于是向量,有贡献当且仅当,而则在为时为负权,因此可以构造最大权闭合子图:为正权点,为负权点,依赖,然后套板子就好了。
#include <bits/stdc++.h>using namespace std;const int MAXN = 501*501+1005;struct node {int to, next, flow, neg;} edge[MAXN*10];int head[MAXN], top = 0;inline void push(int i, int j, int f){++top, edge[top] = (node){j, head[i], f, top+1}, head[i] = top;++top, edge[top] = (node){i, head[j], 0, top-1}, head[j] = top;}int vis[MAXN], bfstime = 0;int lev[MAXN];int S = MAXN-3, T = MAXN-2;queue<int> que;bool bfs(){vis[S] = ++bfstime, lev[S] = 0, que.push(S);while (!que.empty()) {int nd = que.front(); que.pop();for (int i = head[nd]; i; i = edge[i].next) {int to = edge[i].to, f = edge[i].flow;if (vis[to] == bfstime || !f) continue;vis[to] = bfstime, que.push(to), lev[to] = lev[nd]+1;}}return vis[T] == bfstime;}int dfs(int nd, int flow){if (nd == T || !flow) return flow;int ans = 0, t;for (int i = head[nd]; i; i = edge[i].next) {int to = edge[i].to, f = edge[i].flow;if (lev[to] != lev[nd]+1 || !f) continue;t = dfs(to, min(flow, f));ans += t, edge[i].flow -= t;edge[edge[i].neg].flow += t, flow -= t;}if (flow) lev[nd] = -1;return ans;}int dinic(){int ans = 0;while (bfs()) ans += dfs(S, 233333333);return ans;}int B[505][505], n, c[MAXN];int main(){scanf("%d", &n);int ans = 0;for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)scanf("%d", &B[i][j]), ans += B[i][j];for (int i = 1; i <= n; i++)scanf("%d", &c[i]);for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++) {int dat = (i-1)*n+j;push(S, dat, B[i][j]);push(dat, n*n+i, 233333333);push(dat, n*n+j, 233333333);}for (int i = 1; i <= n; i++)push(n*n+i, T, c[i]);cout << ans-dinic() << endl;return 0;}
向Orz stdcall学习了一个上下界最大最小流的姿势:
这题需要当前弧一下,跑得比香港记者还快。
#include <bits/stdc++.h>using namespace std;const int MAXN = 50010, MAXM = 100227*10;struct node {int to, next, flow, neg;} edge[MAXM];int head[MAXN], top = 0;inline void push(int i, int j, int f){++top, edge[top] = (node){j, head[i], f, top+1}, head[i] = top;++top, edge[top] = (node){i, head[j], 0, top-1}, head[j] = top;}int SS = 50007, ST = 50008;int inf = 2147483647;int lev[MAXN], vis[MAXN], bfstime = 0;queue<int> que;int n, m, s, t;bool bfs(){lev[SS] = 1, vis[SS] = ++bfstime;que.push(SS);while (!que.empty()) {int nd = que.front(); que.pop();for (int i = head[nd]; i; i = edge[i].next) {int to = edge[i].to, f = edge[i].flow;if (vis[to] == bfstime || !f) continue;lev[to] = lev[nd]+1, que.push(to), vis[to] = bfstime;}}return vis[ST] == bfstime;}int cur[MAXN];long long dfs(int nd, int maxf){if (nd == ST || !maxf) return maxf;long long ans = 0;int t;for (register int &i = cur[nd]; i; i = edge[i].next) {int to = edge[i].to, f = edge[i].flow;if (lev[to] != lev[nd]+1 || !f) continue;t = dfs(to, min(f, maxf));ans += t, maxf -= t;edge[i].flow -= t, edge[edge[i].neg].flow += t;if (!maxf) break;}if (maxf) lev[nd] = -1;return ans;}long long dinic(){long long ans = 0;while (bfs()) {for (int i = 1; i <= n; i++) cur[i] = head[i];cur[SS] = head[SS], cur[ST] = head[ST];ans += dfs(SS, inf);//, printf("%lld\n", ans);}return ans;}int main(){scanf("%d%d%d%d", &n, &m, &s, &t);long long cnt = 0;for (int i = 1; i <= m; i++) {int u, v, l, r;scanf("%d%d%d%d", &u, &v, &l, &r);push(u, v, r-l), push(SS, v, l), push(u, ST, l);cnt += l;}long long k = dinic();push(t, s, inf);long long ans = dinic();if (k+ans < cnt) puts("please go home to sleep");else cout << ans << endl;return 0;}
好好做一个斜率优化...
首先是一个贪心性质,每次交易一定是的。
然后可以设为第天手上最多,为,为第天最多的钱,根据题目有:
然后找递推关系:
有:
后面的,对于一大堆点要最大化轴截距,则要维护一个上凸壳。然而这次斜率不随单调,因此不能用单调队列,而要用维护凸包。
考虑用分治解决这个问题。首先按斜率排序询问。过程用来求解内所有,并将按横坐标排序。考虑先划分成和;递归处理。然后得到了左边所有节点排好序的结果,用一个单调栈维护上凸壳。右边所有询问按斜率排序过了,直接扫一遍更新。然后就可以递归处理右边。
最后只剩一个问题,就是按排序。这里只要用归并两个区间就可以了。
#include <bits/stdc++.h>using namespace std;const int MAXN = 100005;int n, m;double f[MAXN];const double eps = 1e-7, inf = 1e9;struct query {double a, b, r;int pos;inline double slop(){return -a/b;}} q[MAXN], tmp[MAXN];struct point {double x, y;friend bool operator < (const point &a, const point &b){ return b.x-a.x>eps||(abs(b.x-a.x)<=eps && b.y-a.y > eps); }} stk[MAXN], p[MAXN], tmp_p[MAXN];int top;inline double slop(const point &a, const point &b){if (abs(a.x-b.x)<=eps) return -inf;return (a.y-b.y)/(a.x-b.x);}bool cmp(query a, query b){ return a.slop() > b.slop(); }void solve(int l, int r){if (l == r) {f[l] = max(f[l], f[l-1]);p[l].y = f[l]/(q[l].a*q[l].r+q[l].b), p[l].x = q[l].r*p[l].y;return;}int mid = (l+r)>>1, l1 = l, l2 = mid+1;for (int i = l; i <= r; i++) {if (q[i].pos <= mid) tmp[l1++] = q[i];else tmp[l2++] = q[i];}for (int i = l; i <= r; i++)q[i] = tmp[i];solve(l, mid);top = 0;for (int i = l; i <= mid; i++) {while (top >= 2 && slop(stk[top-1], stk[top]) < slop(stk[top], p[i])-eps) top--;stk[++top] = p[i];}int lp = 1;for (int i = mid+1; i <= r; i++) {while (lp+1 <= top && slop(stk[lp], stk[lp+1]) > q[i].slop()-eps) lp++;f[q[i].pos] = max(f[q[i].pos], stk[lp].x*q[i].a+stk[lp].y*q[i].b);}solve(mid+1, r);int tp = l, a = l, b = mid+1;while (a <= mid && b <= r) {if (p[a] < p[b]) tmp_p[tp++] = p[a++];else tmp_p[tp++] = p[b++];}while (a <= mid) tmp_p[tp++] = p[a++];while (b <= r) tmp_p[tp++] = p[b++];for (int i = l; i <= r; i++)p[i] = tmp_p[i];}int main(){// freopen("cash.in", "r", stdin);// freopen("cash.out", "w", stdout);scanf("%d%lf", &n, &f[0]);for (int i = 1; i <= n; i++) {scanf("%lf%lf%lf", &q[i].a, &q[i].b, &q[i].r);q[i].pos = i;}sort(q+1, q+n+1, cmp);solve(1, n);printf("%.3f\n", f[n]);return 0;}
题意:给定主串和模式串,每个模式串有权值,在某个位置匹配一次可以获取权值。主串每个位置最多被匹配次,问最大权值和。
首先用自动机找出所有的匹配位置(记录所有经过的位置,他在树上的祖先即是所有匹配位置),然后就变成了一个区间最大权覆盖的模型。
然后就是网络流套路(类似志愿者招募那个模型)了:
考虑增广路的形态,显然由于从左向右连,不会有相交环干扰,每一条增广路都是不相交的环组成的。而流量则限制了跨越他的环最多只有个,而下面的流量,所以存在一个的割,根据最大流最小割定理,,满足了最多被覆盖次的限制。
#include <bits/stdc++.h>using namespace std;int n, m, p[105], x;int len[105];char str[505], s[505];const int MAXN = 500*100;struct node {int to, next, flow, cost, neg;} edge[500*500];int head[MAXN], tp = 0;inline void push(int i, int j, int f, int c){++tp, edge[tp] = (node){j, head[i], f, c, tp+1}, head[i] = tp;++tp, edge[tp] = (node){i, head[j], 0, -c, tp-1}, head[j] = tp;}int dis[MAXN], vis[MAXN];queue<int> q;int pre[MAXN], pre_edge[MAXN];int S = MAXN-3, T = MAXN-2;bool spfa(int &flow, int &cost){memset(dis, 127/3, sizeof dis);dis[S] = 0, q.push(S), vis[S] = 1;while (!q.empty()) {int nd = q.front(); q.pop(); vis[nd] = 0;// cerr << nd << endl;for (int i = head[nd]; i; i = edge[i].next) {int f = edge[i].flow, c = edge[i].cost;int to = edge[i].to;if (!f || dis[to] <= dis[nd]+c) continue;dis[to] = dis[nd]+c, pre[to] = nd, pre_edge[to] = i;if (!vis[to]) vis[to] = 1, q.push(to);}}if (dis[T] >= 233333333) return 0;int mf = INT_MAX;for (int i = T; i != S; i = pre[i]) mf = min(mf, edge[pre_edge[i]].flow);for (int i = T; i != S; i = pre[i]) edge[pre_edge[i]].flow -= mf, edge[edge[pre_edge[i]].neg].flow += mf;flow += mf, cost += mf*dis[T];return 1;}void mcf(int &flow, int &cost){flow = cost = 0;while (spfa(flow, cost));// printf("...%d\n", cost);}void push_edge(int l, int r, int v){push(l, r+1, 1, -v);}void push_init(){for (int i = 1; i <= n; i++)push(i, i+1, x, 0);push(S, 1, x, 0), push(n+1, T, x, 0);}int fail[MAXN], chl[MAXN][26], fin[MAXN];vector<int> pos[MAXN];int root = 0, top = 0;void push(int &nd, const char *str, int id){if (!nd) nd = ++top;if (*str == '\0') fin[nd] = id;else push(chl[nd][*str-'a'], str+1, id);}queue<int> que;void init(){fail[root] = 0, que.push(root);while (!que.empty()) {int nd = que.front(); que.pop();for (int i = 0; i < 26; i++) {if (!chl[nd][i]) continue;int p = fail[nd];while (p && !chl[p][i]) p = fail[p];if (p) fail[chl[nd][i]] = chl[p][i];else fail[chl[nd][i]] = root;que.push(chl[nd][i]);}}}void match(int nd, const char *str, int ps){for (int i = nd; i; i = fail[i])if (fin[i]) push_edge(ps-len[fin[i]]+1, ps, p[fin[i]]);if (*str == '\0') return;while (nd && !chl[nd][*str-'a']) nd = fail[nd];if (!nd) match(root, str+1, ps+1);else match(chl[nd][*str-'a'], str+1, ps+1);}int main(){scanf("%d", &n);scanf("%s", str);scanf("%d", &m);for (int i = 1; i <= m; i++) {scanf("%s %d", s, &p[i]), len[i] = strlen(s);push(root, s, i);}scanf("%d", &x);push_init();init();match(root, str, 0);int flow = 0, cost = 0;mcf(flow, cost);cout << -cost << endl;return 0;}
广义容斥原理...
http://www.cnblogs.com/candy99/p/6617195.html
#include <bits/stdc++.h>using namespace std;const int MAXN = 2005;int sug[MAXN], med[MAXN], n, k;int cnt[MAXN], fac[MAXN];const int mod = 1e9+9;int f[MAXN][MAXN], a[MAXN];int power(int a, int n){int b = a, ans = 1;for (int i = 0; i <= 30; i++) {if (n&(1<<i)) ans = (long long)ans*b%mod;b = (long long)b*b%mod;}return ans;}int inv(int a){ return power(a, mod-2); }int choose(int n, int k){ return (long long)fac[n]*inv(fac[k])%mod*inv(fac[n-k])%mod; }int main(){scanf("%d%d", &n, &k);if ((n+k)&1) { printf("0\n"); return 0; }else k = (n+k)>>1;for (int i = 1; i <= n; i++) scanf("%d", &sug[i]);for (int i = 1; i <= n; i++) scanf("%d", &med[i]);fac[0] = 1;for (int i = 1; i <= n; i++) fac[i] = (long long)fac[i-1]*i%mod;sort(sug+1, sug+n+1), sort(med+1, med+n+1);int l = 0;for (int i = 1; i <= n; i++) {while (l < n && med[l+1] < sug[i]) l++;cnt[i] = l;}f[0][0] = 1;for (int i = 1; i <= n; i++) {for (int j = 0; j <= n; j++) {f[i][j] = f[i-1][j];if (j>0)f[i][j] = (f[i][j]+(long long)f[i-1][j-1]*max(cnt[i]-j+1, 0)%mod)%mod;}}for (int i = 0; i <= n; i++) a[i] = (long long)f[n][i]*fac[n-i]%mod;int ans = 0;for (int i = k; i <= n; i++)ans = (ans+(((i-k)&1)?-1:1)*(long long)choose(i, k)*a[i]%mod)%mod;cout << (ans%mod+mod)%mod << endl;return 0;}
常规方法不太好处理。
hzwer给出了一个思路:将询问按排序,用数据结构维护当前左端点下各个右端点的结果。然后计算每次移动左区间对后面值的影响。
具体到这道题,首先用并查集或者树状数组上二分求出初始时的,并预处理出每个值下一个这种值的位置。考虑更新到的影响就是将中所有大于的变成,也就是取。这显然可以用线段树维护。
#include <bits/stdc++.h>using namespace std;const int MAXN = 200005, inf = 233333333;int fa[MAXN];inline int findf(int nd){ return fa[nd]!=nd?fa[nd]=findf(fa[nd]):nd; }void link(int i, int j){int a = findf(i), b = findf(j);if (a != b) fa[a] = b;}int a[MAXN], nxt[MAXN], now[MAXN];int n, q;int mex[MAXN];struct tree {int l[MAXN*4], r[MAXN*4], lc[MAXN*4], rc[MAXN*4], tag[MAXN*4], dat[MAXN*4];int top, root;void clear(){ top = root = 0; }void build(int &nd, int opl, int opr){nd = ++top, l[nd] = opl, r[nd] = opr, tag[nd] = inf;if (opl < opr)build(lc[nd], opl, (opl+opr)>>1), build(rc[nd], ((opl+opr)>>1)+1, opr);else dat[nd] = mex[opl];}void pdw(int nd){if (tag[nd] == inf) return;if (lc[nd]) tag[lc[nd]] = min(tag[lc[nd]], tag[nd]), tag[rc[nd]] = min(tag[rc[nd]], tag[nd]), tag[nd] = inf;else dat[nd] = min(dat[nd], tag[nd]), tag[nd] = inf;}void modify(int nd, int opl, int opr, int dt) // 区间内大于dt的变为dt{if (opl > opr) return;pdw(nd);if (l[nd] == opl && r[nd] == opr) tag[nd] = min(tag[nd], dt);else {int mid = (l[nd]+r[nd])>>1;if (opr <= mid) modify(lc[nd], opl, opr, dt);else if (opl > mid) modify(rc[nd], opl, opr, dt);else modify(lc[nd], opl, mid, dt), modify(rc[nd], mid+1, opr, dt);}}int query(int nd, int dt){pdw(nd);if (l[nd] == r[nd]) return dat[nd];else {int mid = (l[nd]+r[nd])>>1;if (dt <= mid) return query(lc[nd], dt);else return query(rc[nd], dt);}}} seg;int last[MAXN];struct qy {int l, r, id, ans;friend bool operator < (const qy &a, const qy &b){ return a.l < b.l; }} qs[MAXN];int ans[MAXN];int main(){scanf("%d%d", &n, &q);for (int i = 0; i <= n; i++) fa[i] = i;for (int i = 1; i <= n; i++)scanf("%d", &a[i]);for (int i = 1; i <= n; i++) {now[a[i]] = 1;if (a[i]-1>=0&&now[a[i]-1]) link(a[i]-1, a[i]);if (now[a[i]+1]) link(a[i], a[i]+1);mex[i] = now[0]?findf(0)+1:0;} // 并查集维护初始mexfor (int i = 1; i <= n; i++) {if (last[a[i]]) nxt[last[a[i]]] = i;last[a[i]] = i;}for (int i = 1; i <= n; i++)if (nxt[i] == 0) nxt[i] = n+1;seg.build(seg.root, 1, n); // 初始值for (int i = 1; i <= q; i++) scanf("%d%d", &qs[i].l, &qs[i].r), qs[i].id = i;sort(qs+1, qs+q+1);int now = 1;for (int i = 1; i <= n; i++) {while (now <= q && qs[now].l == i) {ans[qs[now].id] = seg.query(seg.root, qs[now].r);now++;}seg.modify(seg.root, i, nxt[i]-1, a[i]);}for (int i = 1; i <= q; i++)printf("%d\n", ans[i]);return 0;}
1A了...感慨万千...
OI中摩尔定律:知识量随时间以指数增长,题目难度随时间以指数降低。
#include <bits/stdc++.h>using namespace std;const int MAXN = 200005;struct node {int to, next, dis;} edge[MAXN*2];int head[MAXN], top = 0;void push(int i, int j, int d){ edge[++top] = (node){j, head[i], d}, head[i] = top; }int siz[MAXN], vis[MAXN];void dfs_siz(int nd, int f){siz[nd] = 1;for (int i = head[nd]; i; i = edge[i].next) {int to = edge[i].to;if (to == f || vis[to]) continue;dfs_siz(to, nd), siz[nd] += siz[to];}}void dfs_sel(int nd, int f, const int totsiz, int &ans, int &maxl){int maxs = 0;if (f) maxs = totsiz-siz[nd];for (int i = head[nd]; i; i = edge[i].next) {int to = edge[i].to;if (to == f || vis[to]) continue;maxs = max(maxs, siz[to]);}if (maxs < maxl) maxl = maxs, ans = nd;for (int i = head[nd]; i; i = edge[i].next) {int to = edge[i].to;if (to == f || vis[to]) continue;dfs_sel(to, nd, totsiz, ans, maxl);}}int sig[1000005];int n, k, pans = 233333333, cnt = 233333333;void dfs_calc(int nd, int f, int dep, int now_len){if (now_len > k) return;cnt = min(cnt, sig[k-now_len]+dep);for (int i = head[nd]; i; i = edge[i].next) {int to = edge[i].to;if (to == f || vis[to]) continue;dfs_calc(to, nd, dep+1, now_len+edge[i].dis);}}void dfs_mod(int nd, int f, int dep, int now_len){if (now_len > k) return;sig[now_len] = min(sig[now_len], dep);for (int i = head[nd]; i; i = edge[i].next) {int to = edge[i].to;if (to == f || vis[to]) continue;dfs_mod(to, nd, dep+1, now_len+edge[i].dis);}}void dfs_dec(int nd, int f, int dep, int now_len){if (now_len > k) return;sig[now_len] = 233333333;for (int i = head[nd]; i; i = edge[i].next) {int to = edge[i].to;if (to == f || vis[to]) continue;dfs_dec(to, nd, dep+1, now_len+edge[i].dis);}}void calc(int nd){dfs_siz(nd, 0);int center = 0, ans = 233333333;dfs_sel(nd, 0, siz[nd], center, ans);// cerr << center << endl;vis[center] = 1, cnt = 233333333, sig[0] = 0;for (int i = head[center]; i; i = edge[i].next) {int to = edge[i].to, d = edge[i].dis;if (vis[to]) continue;dfs_calc(to, 0, 1, d), dfs_mod(to, 0, 1, d);// cerr << to << endl;// for (int j = 1; j <= k; j++)// cerr << sig[j] << " ";// cerr << endl << cnt << endl;}pans = min(pans, cnt);// cerr << "gg" << ans << endl;dfs_dec(center, 0, 0, 0);for (int i = head[center]; i; i = edge[i].next) {int to = edge[i].to;if (vis[to]) continue;calc(to);}}int main(){freopen("ioi2011-race.in", "r", stdin);freopen("ioi2011-race.out", "w", stdout);int size = 128 << 20;char *p = (char*)malloc(size) + size;__asm__("movl %0, %%esp\n" :: "r"(p));memset(sig, 127/3, sizeof sig);scanf("%d%d", &n, &k);for (int i = 1; i < n; i++) {int u, v, d;scanf("%d%d%d", &u, &v, &d), u++, v++;push(u, v, d), push(v, u, d);}calc(1);if (pans < 2333333)cout << pans << endl;else puts("-1");return 0;}
学习了一个动态加边。
#include <bits/stdc++.h>using namespace std;const int MAXN = 40+80*100+5;struct node {int to, next, flow, cost, neg;} edge[MAXN*20];int head[MAXN], top = 0;void push(int i, int j, int f, int c){// printf("%d--%d,%d-->%d\n", i, f, c, j);++top, edge[top] = (node){j, head[i], f, c, top+1}, head[i] = top;++top, edge[top] = (node){i, head[j], 0, -c, top-1}, head[j] = top;}int dis[MAXN], vis[MAXN], pre[MAXN], pre_edge[MAXN];queue<int> que;int S = MAXN-5, T = MAXN-4;int inf = 23333333;bool spfa(int &flow, int &cost){memset(dis, 127/3, sizeof dis);memset(vis, 0, sizeof vis);vis[S] = 1, que.push(S), dis[S] = 0;while (!que.empty()) {int nd = que.front(); que.pop(); vis[nd] = 0;// cerr << nd << endl;for (int i = head[nd]; i; i = edge[i].next) {int to = edge[i].to, f = edge[i].flow, c = edge[i].cost;if (!f || dis[to] <= dis[nd]+c) continue;dis[to] = dis[nd]+c;pre[to] = nd, pre_edge[to] = i;if (!vis[to])vis[to] = 1, que.push(to);}}// cerr << dis[T] << endl;if (dis[T] > inf) return 0;int maxf = inf;for (int i = T; i != S; i = pre[i]) maxf = min(maxf, edge[pre_edge[i]].flow);for (int i = T; i != S; i = pre[i]) edge[pre_edge[i]].flow -= maxf, edge[edge[pre_edge[i]].neg].flow += maxf;flow += maxf, cost += maxf*dis[T];return 1;}int id[MAXN], k[MAXN], dish[MAXN], n, m;int top_tp = 0;int t[45][505], p[45];void mcf(int &flow, int &cost){flow = cost = 0;while (spfa(flow, cost)) {int x = pre[T];// cerr << "id " << x << endl;x = id[x];k[x]++, id[++top_tp] = x;for (int i = 1; i <= n; i++)push(dish[i], top_tp, 1, k[x]*t[i][x]);push(top_tp, T, 1, 0);// printf("---%d\n", flow);}}int main(){scanf("%d%d", &n, &m);for (int i = 1; i <= n; i++) scanf("%d", &p[i]);for (int i = 1; i <= n; i++)for (int j = 1; j <= m; j++)scanf("%d", &t[i][j]);for (int i = 1; i <= n; i++) dish[i] = ++top_tp;for (int i = 1; i <= n; i++) push(S, dish[i], p[i], 0);for (int i = 1; i <= m; i++) {id[++top_tp] = i, k[i]++;for (int j = 1; j <= n; j++)push(dish[j], top_tp, 1, k[i]*t[j][i]);push(top_tp, T, 1, 0);}int flow, cost;// puts("---");mcf(flow, cost);cout << cost << endl;return 0;}