六省联考2017 题解

D2T1 啥毒瘤题啊,本篇题解不涉及。不过有一说一 D2T3 还是非常人性化的。

P3745 [六省联考 2017] 期末考试

\(m\) 门考试,第 \(i\) 门考试原计划在第 \(b_i\) 天出成绩。有 \(n\) 个学生在等待成绩,第 \(i\) 个学生希望在第 \(t_i\) 天前得知所有的成绩。设最晚出成绩的那门考试在第 \(T\) 天,则有以下三种情况会产生不愉快度。

  • 每一名学生会产生 \(C\max(T-t_i,0)\) 的不愉快度。
  • 将第 \(x\) 门考试推迟一天出成绩并将第 \(y\) 门考试提前一天出成绩,产生 \(A\) 的不愉快度。
  • 将第 \(x\) 门考试提前一天出成绩,产生 \(B\) 的不愉快度。

求可能的不愉快度最小值。(\(1\le n,m,t_i,b_i\le 10^5,0\le A,B,C\le 10^5\))

注意到调整后的 \(T\) 一定不不调整的 \(T\) 更小,且注意到 \(T\le 10^5\),所以考虑枚举调整后的 \(T\),并计算调整到 \(T\) 的代价,计算出学生的不愉快度后取个 \(\min\) 即可。而想要调整到 \(T\),会有一些科目 需要提前,还有一些科目 可以推迟。现在我们需要找到一个贪心策略来调整。

注意到如果 \(B<A\),则直接用费用更低且没有限制的操作即可,如果总共需要提前 \(x\) 天,则产生的代价显然为:

\[Bx \]

而如果 \(A<B\),则我们需要尽可能利用可以推迟的科目,即总共如果需要提前 \(x\) 天,可以推迟 \(y\) 天,则产生的代价为:

\[A\min(x,y)+B\max(0,x-y) \]

注意到只要知道 \(x,y\),根据贪心策略我们都可以在 \(\mathcal{O}(1)\) 的时间计算出需要的调整时间。直接算看起来好像不太可行,所以我们考虑每次 \(T\) 减小时会对 \(x,y\) 分别造成什么影响。首先,如果对每门考试的 \(b_i\) 排序,我们可以用一个指针在均摊 \(\mathcal{O}(1)\) 的时间下求出在 \(T\) 前和后的考试科目,分别设为 \(las,nxt\),和 \(b_i=T\) 的科目个数,设为 \(cnt\),则 \(x,y\) 分别会变为:

\[x+nxt-cnt,y-las-cnt \]

这样我们就能在均摊 \(\mathcal{O}(1)\) 的时间复杂度内维护 \(x,y\) 了。

而知道代价之后,我们还要知道学生产生不满意度 \(now\)。注意到把 \(t_i\) 排序后依然是满足单调性的,所以还考虑用一个指针维护有多少学生会产生不满意度。对于之前产生现在不产生的学生,设为 \(l\),则 \(now\) 会变为:

\[now-lC \]

而对于其他的,设为 \(p\)\(now\) 类似地,会变为:

\[now-pC \]

这俩不一样的是,\(lC\) 的贡献之后不会再产生了。而一开始的 \(now\) 可以 \(\mathcal{O}(n)\) 枚举求出,并找到指针的初始位置。至此,我们就能在 \(\mathcal{O}(1)\) 的时间复杂度内维护好所有需要的信息,总时间复杂度 \(\mathcal{O}(n+m+\max t_i)\)

#include <cstdio>
#include <algorithm>
const int N = 1e5 + 10; typedef long long ll;
int a[N], b[N], A, B, C, n, m;
int main()
{
    scanf("%d%d%d%d%d", &A, &B, &C, &n, &m); ll ans = 1e18;
    for (int i = 1; i <= n; ++i) scanf("%d", a + i);
    for (int i = 1; i <= m; ++i) scanf("%d", b + i);
    std::sort(a + 1, a + n + 1); std::sort(b + 1, b + m + 1);
    ll res = 0, ned = 0, now = 0, add; int posa = n, posb = m;
    while (posb >= 1 && b[posb] == b[m]) --posb;
    for (int i = 1; i < m; ++i) res += (b[m] - b[i]);
    for (int i = 1; i <= n; ++i) 
    {
        if (a[i] >= b[m]) { posa = i - 1; break; }
        now += (ll)C * (b[m] - a[i]); 
    }
    ans = std::min(ans, now);
    for (int i = b[m] - 1, cnt; i >= 1; --i)
    {
        cnt = 0;
        while (posb >= 1 && b[posb] == i) --posb, ++cnt;
        res -= posb + cnt; ned += m - posb - cnt;
        if (A < B && res > 0) add = std::min(res, ned) * A + std::max(0ll, ned - res) * B;
        else add = ned * B;
        while (posa >= 1 && a[posa] == i) now -= C, --posa; 
        now -= (ll)posa * C; ans = std::min(ans, now + add);
    }
    printf("%lld\n", ans); return 0;
}

P3747 [六省联考 2017] 相逢是问候

维护一个长为 \(n\) 的数组,给出常数 \(c,p\),有以下两种操作:

  • 将第 \(l\) 到第 \(r\) 个数中的每个 \(a_i\) 替换为 \(c^{a_i}\)
  • 求出 \(\sum\limits_{i=l}^ra_i\bmod{p}\)

(\(1\le n,m\le 5\times10^4,1\le c,a_i<p\le 10^8\))

发现题目与指数有关,考虑拓展欧拉定理:

\[a^b=\begin{cases}a^{b\bmod{\varphi(m)}}&\gcd(a,m)=1\\a^b&b<\varphi(m)\\a^{b\bmod{\varphi(m)}+\varphi(m)}&b\ge \varphi(m)\end{cases}\bmod{m} \]

注意到题目不保证 \(\gcd(p,c)=1\),所以考虑后两个等式。如果您做过 P4139 上帝与集合的正确用法 这道题的话,就会知道,像本题给出的指数叠叠高的形式在模意义下是有尽头的,即超过一定层数后的指数就不会产生贡献了。可以通过考虑把指数看为递归形式,而 \(\varphi(\varphi(\varphi\cdots(m)))\) 的形式,会在一定次数后变为 \(1\),超过这个次数后的指数就变为 \(0\) 了。而在本题中,一旦 \(a_i\) 变为 \(0\),之后再操作值就不会改变了,一直为:

\[c^{c^{c^{\cdots}}} \]

而又注意到,\(\varphi(\varphi(m))\) 至少会把 \(m\) 缩小 \(2\) 倍,考虑 \(\varphi(m)\) 的一种求法:

\[\varphi(m)=\prod_{i=1}^k\varphi(p_i^{c_i})=\prod_{i=1}^k(p_i^{c_i}-p_i^{c_i-1})=\prod_{i=1}^kp_i^{c_i-1}(p_i-1)=m\prod_{i=1}^k\dfrac{p_i-1}{p_i} \]

注意到如果 \(m\) 为奇数则至少会有一个积的质因子变为偶数,这样 \(\varphi(m)\) 就会变为偶数。而如果 \(m\) 为偶数,则 \(2\) 会变为 \(1\),所以不管 \(m\) 的值是多少,\(\varphi(\varphi(m))\) 至少把 \(m\) 缩小 \(2\) 倍。这样的话,能发现需要考虑的层数非常少,仅有 \(\mathcal{O}(\log p)\) 层。

发现了什么?虽然这个区间操作并不好直接维护,但有影响的操作次数很少!这不免让我们联想起线段树维护区间开方这种利用势能分析保证复杂度维护方法。所以类比一下,我们考虑用线段树维护这个操作,考虑在每个结点维护两个值。

  • \(minx\) 表示当前区间的最高层数。
  • \(sum\) 表示当前区间的区间和。

而显然如果一个区间的 \(minx\) 都大于最高层数,我们就不用再对这个区间进行操作了,否则就递归下去暴力单点操作。可以用我不会的势能分析证明这个复杂度是正确的 \(\mathcal{O}(m\log p+n\log n)\)

现在我们要解决的问题一下子就变成了如何对一个单点进行修改。注意到我们记录了这个区间叠高高叠了多少层,所以可以类比 P4139 上帝与集合的正确用法 的方法,用递归的形式计算,这就要求我们知道 \(\varphi(p),\varphi(\varphi(p)),\cdots\) 的值,但因为 \(p\)\(10^8\) 级别,直接线性筛显然是不行的,但我们可以用刚刚提到的方法提前暴力算出这些值,时间复杂度 \(\mathcal{O}(\sqrt{p}\log p)\),可以接受。而递归的层数是 \(\log p\) 层,每层需要一次 \(\log p\) 的快速幂,这样一次计算就是 \(\mathcal{O}(\log^2p)\) 的时间复杂度,总的复杂度即为 \(\mathcal{O}(\sqrt{p}\log p+m\log^3p+n\log n)\),非常难以通过。

回顾刚刚的过程,几乎所有的过程都难以再优化了,除了快速幂的那只 \(\log\),考虑预处理幂和幂的大小是否超过模数,用光速幂做到 \(\mathcal{O}(1)\)。但注意要对所有可能出现的 \(\varphi\) 预处理,所以时间复杂度为 \(\mathcal{O}(\sqrt{m}\log p)\),这样总时间复杂度就被优化至 \(\mathcal{O}((\sqrt{p}+\sqrt{m})\log p+m\log^2p+n\log n)\),足以通过。

#include <cstdio>
#include <algorithm>
const int N = 5e4 + 10, D = 100, B = 1e4; typedef long long ll;
int pw[2][D][B + 10], ov[2][D][B + 10], phi[D], dep, n, m, p, c, over, a[N];
inline int Phi(int n)
{
	int res = n;
	for (int i = 2; i * i <= n; ++i)
	{
		if (n % i == 0) res = res / i * (i - 1);
		while (n % i == 0) n /= i; 
	}
	if (n > 1) res = res / n * (n - 1); return res;
}
inline void pre()
{
	phi[0] = p; ll t;
	while (phi[dep] > 1) ++dep, phi[dep] = Phi(phi[dep - 1]);
	phi[++dep] = 1;
	for (int i = 0; i <= dep; ++i)
	{
		pw[0][i][0] = 1;
		for (int j = 1; j <= B; ++j)
		{
			t = (ll)pw[0][i][j - 1] * c; if (t >= phi[i]) ov[0][i][j] = 1;
			pw[0][i][j] = t % phi[i]; ov[0][i][j] |= ov[0][i][j - 1];
		}
		pw[1][i][0] = 1;
		for (int j = 1; j <= B; ++j)
		{
			t = (ll)pw[1][i][j - 1] * pw[0][i][B]; if (t >= phi[i]) ov[1][i][j] = 1;
			pw[1][i][j] = t % phi[i]; ov[1][i][j] |= ov[1][i][j - 1];
		}
	}
} 
inline int gsm(int d, int b)
{
	int v1 = b / B, v2 = b % B; ll t;
	over |= ov[0][d][v2] | ov[1][d][v1];
	t = (ll)pw[0][d][v2] * pw[1][d][v1]; if (t >= phi[d]) over = 1;
	return t % phi[d];
}
int calc(int p, int cnt, int d)
{
	over = 0;
	if (d == cnt)
	{
		if (a[p] >= phi[d]) over = 1;
		return a[p] % phi[d];
	}
	int x = calc(p, cnt, d + 1);
	if (over) x += phi[d + 1], over = 0;
	return gsm(d, x);
}
struct SegTree
{
	#define ls(k) (k << 1)
	#define rs(k) (k << 1 | 1)
	struct node{ int l, r, cnt, sum; }h[N << 2];
	void pushup(int k) 
	{ 
		h[k].cnt = std::min(h[ls(k)].cnt, h[rs(k)].cnt); 
		h[k].sum = (h[ls(k)].sum + h[rs(k)].sum) % p;
	}
	void build(int k, int l, int r)
	{
		h[k].l = l; h[k].r = r;
		if (l == r) return h[k].sum = a[l], void();
		int mid = (l + r) >> 1; build(ls(k), l, mid); build(rs(k), mid + 1, r);
		pushup(k);
	}
	void change(int k, int x, int y)
	{
		if (h[k].cnt >= dep) return ;
		if (h[k].l == h[k].r) return ++h[k].cnt, h[k].sum = calc(h[k].l, h[k].cnt, 0), void();
		int mid = (h[k].l + h[k].r) >> 1; 
		if (x <= mid) change(ls(k), x, y); if (mid < y) change(rs(k), x, y);
		pushup(k);
	}
	int query(int k, int x, int y)
	{
		if (x <= h[k].l && h[k].r <= y) return h[k].sum;
		int mid = (h[k].l + h[k].r) >> 1, ret = 0;
		if (x <= mid) (ret += query(ls(k), x, y)) %= p;
		if (mid < y) (ret += query(rs(k), x, y)) %= p;
		return ret;
	}
}sgt;
int main()
{
	scanf("%d%d%d%d", &n, &m, &p, &c); pre();
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	sgt.build(1, 1, n);
	for (int i = 1, op, l, r; i <= m; ++i)
		scanf("%d%d%d", &op, &l, &r), op ? printf("%d\n", sgt.query(1, l, r)), void() : sgt.change(1, l, r);
	return 0;
}

P3746 [六省联考 2017] 组合数问题

给出 \(n,p,k,r\),求下列式子的值:

\[\left(\sum_{i\ge0}\dbinom{nk}{ik+r}\right)\bmod{p} \]

(\(1\le n\le 10^9,0\le r<k\le 50,2\le p\le 2^{30}-1\))

首先如果 \(p\) 保证是质数,即一定存在逆元,可以用定义式算,用下式在 \(\mathcal{O}(nk)\) 的时间复杂度下进行递推计算:

\[\dbinom{nk}{(i+1)k+r}=\dbinom{nk}{ik+r}\times\dfrac{(nk-ik-r)^{\underline{k}}}{(ik+k+r)^{\underline{k}}} \]

证明可以通过把组合数拆成阶乘看出,期望得分 \(\tt 60pts\)

但当 \(n\) 比较大,或者 \(p\) 不保证质数时,上面的方法就无法采用了。因为逆元不一定存在,从数学这方面的推导几乎就被封死了,所以我们可以从组合数本身进行思考。注意到组合数 \(\binom{n}{m}\) 的组合意义是从 \(n\) 个物品里选 \(m\) 个的方案数,而套用到这道题上,我们会得到:

  • \(nk\) 个物品里选 \(r,k+r,2k+r,\cdots\) 个物品的方案数之和。

而这些物品个数有很强的共性:均模 \(k\)\(r\)。而 \(k\) 又很小,这启发我们把问题控制在 \(k\) 的剩余系内。考虑设 \(f_{i,j}\) 表示考虑了 \(i\) 个物品,选出的个数模 \(k\)\(j\) 的方案数,则最终答案即为 \(f_{nk,r}\),枚举每个物品是否选取,可以得到以下转移方程:

\[f_{i-1,j}+f_{i-1,(j-1)\bmod{k}}\rightarrow f_{i,j},f_{0,0}=1 \]

直接暴力转移是 \(\mathcal{O}(nk^2)\),不如暴力。注意到这个形式是常系数齐次线性递推的形式,可以考虑用矩乘优化:

\[\begin{bmatrix}f_{i,0}&f_{i,1}&\cdots&f_{i,k-1}\end{bmatrix}\begin{bmatrix}1&0&\cdots&0&1\\1&1&\cdots&0&0\\\vdots&\vdots&\ddots&\vdots&\vdots\\0&0&\cdots&1&1\end{bmatrix}=\begin{bmatrix}f_{i+1,0}&f_{i+1,1}&\cdots&f_{i+1,k-1}\end{bmatrix} \]

实现个矩阵快速幂就可以在 \(\mathcal{O}(k^3\log (nk))\) 的时间复杂度内求解了。

#include <cstdio>
const int N = 60; typedef long long ll; int n, p, k, r;
struct Matrix
{
    int a[N][N], n, m;
    int* operator[](int n) { return a[n]; }
    void init(int N, int M, int on = 0)
    {
        n = N; m = M;
        for (int i = 1; i <= n; ++i)
                for (int j = 1; j <= m; ++j) a[i][j] = on ? (i == j) : 0;
    }
    Matrix operator*(Matrix A)
    {
        Matrix ret; ret.init(n, A.m);
        for (int i = 1; i <= ret.n; ++i)
            for (int j = 1; j <= ret.m; ++j)
                for (int k = 1; k <= m; ++k) (ret[i][j] += (ll)a[i][k] * A[k][j] % p) %= p;
        return ret;
    }
}A, B, I;
Matrix ksm(Matrix A, ll b)
{
    Matrix ret = I;
    while (b)
    {
        if (b & 1) ret = ret * A;
        A = A * A; b >>= 1;
    }
    return ret;
}
int main()
{
    scanf("%d%d%d%d", &n, &p, &k, &r);
    A.init(1, k); B.init(k, k, 1); I.init(k, k, 1); A[1][1] = 1;
    for (int i = 2; i <= k; ++i) ++B[i][i - 1]; ++B[1][k];
    B = ksm(B, (ll)n * k); A = A * B; printf("%d\n", A[1][r + 1]); return 0;
}

P3748 [六省联考 2017] 摧毁“树状图”

\(\tt deleted\)

P3750 [六省联考 2017] 分手是祝愿

\(n\) 个灯,初始时有亮有暗,按下第 \(i\) 个开关会导致所有的 \(d\) 满足 \(d|i\) 对应位置的灯改变亮暗。现在一个人随机按开关,但当当前状态下最优策略可以在 \(\le k\) 步将灯全部关掉,这个人就会按照最优策略操作。求这个人的期望操作次数乘 \(n!\) 的值,答案对 \(10^5+3\) 取模。(\(1\le n\le 10^5,0\le k\le n\))

我们先不管题目,来考虑一下对于给出状态的灯,怎么操作才是最优的。注意到一次按开关,能影响到的都是编号比它小的,而我们只要不走回头路就能保证操作次数最小了,即我们按照编号从大到小考虑灯,如果亮就按开关,并计算影响,否则就不管。这样计算的时间复杂度是 \(\mathcal{O}(n\sqrt{n})\),完全没有问题。证明可以感性理解,我们只按必须按的开关。

现在我们知道了最大操作次数 \(m\),接下来就该算期望了。考虑 \(\rm dp\),设 \(f_i\) 表示最优策略是按 \(i\) 下开关时需要的期望操作次数。注意到开关造成的影响是唯一的,即不存在一个开关组可以替换某个开关,所以如果随机按开关按错了(即不在最优策略里)之后还一定要按一次按回来,所以有转移:

\[f_i=\dfrac{i}{n}f_{i-1}+\dfrac{n-i}{n}f_{i+1}+1 \]

好了,做个高消,取 \(f_m\) 这道题就做完了,时间复杂度 \(\mathcal{O}(n)\)

但非常遗憾我已经不会高消了,所以我们来再盯着这个式子看一会儿。发现这个式子需要高消的根本原因是有三项,无法递推,那我们干脆就把它变成一项!考虑差分数组:

\[d_i=f_i-f_{i-1} \]

则原式可以化为:

\[f_i=\dfrac{i}{n}(f_i-d_i)+\dfrac{n-i}{n}(f_i+d_{i+1})+1 \]

简单的代数变化后,你会发现,\(f_i\) 从两边消去了!然后我们得到了什么呢?一个关于 \(d_i\) 的递推式!

\[d_i=\dfrac{n-i}{i}d_{i+1}+\dfrac{n}{i} \]

这东西可以倒着推,所以现在我们的问题就是找到一个 \(d_i(i\ge m)\) 的值倒着推回来,再根据 \(f_k=k\),正着推到 \(f_m\) 即为答案。考虑最小操作次数为 \(n\) 的情况,这时候不管按哪个开关都在最优策略内,这样关于 \(f\) 的等式就少了 \(f_{i+1}\) 这一项,即:

\[f_n=f_{n-1}+1 \]

所以我们就得到:

\[d_n=f_n-f_{n-1}=1 \]

而又因为 \(m\le n\),所以我们就找到了一个符合条件的值!直接递推即可,总时间复杂度 \(\mathcal{O}(n\sqrt{n})\)。注意特判 \(k\ge m\) 的情况。

#include <cstdio>
const int N = 1e6 + 10, mod = 100003; int a[N], f[N], d[N];
inline int ksm(int a, int b)
{
    int ret = 1;
    while (b)
    {
        if (b & 1) ret = 1ll * ret * a % mod;
        a = 1ll * a * a % mod; b >>= 1;
    }
    return ret;
}
int main()
{
    int n, m = 0, k, fac = 1; scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), fac = 1ll * fac * i % mod;
    for (int i = n; i >= 1; --i)
    {
        if (!a[i]) continue;
        for (int j = 1; j * j <= i; ++j)
        {
            if (i % j) continue;
            a[j] ^= 1; if (j * j != i) a[i / j] ^= 1;
        }
        ++m;
    }
    if (m <= k) return printf("%lld\n", 1ll * m * fac % mod), 0;
    d[n] = 1;
    for (int i = n - 1, inv; i >= k; --i) 
    {
        inv = ksm(i, mod - 2);
        d[i] = (1ll * inv * (n - i) % mod * d[i + 1] % mod + 1ll * inv * n % mod) % mod;
    }
    f[k] = k;
    for (int i = k + 1; i <= m; ++i) f[i] = (f[i - 1] + d[i]) % mod;
    f[m] = 1ll * f[m] * fac % mod; printf("%d\n", f[m]); return 0; 
}

P3749 [六省联考 2017] 寿司餐厅

一家餐厅出售 \(n\) 种寿司,每种寿司有一个代号 \(a_i\),每次购买寿司的种类必须形成一段区间。吃掉区间 \([l,r]\) 内的寿司会产生 \(d_{l,r}\) 的美味度,但每种美味度在最终计算中只会计入一次。餐厅的收费方式是如果吃了 \(c\) 种代号为 \(x\) 的寿司,则需要支付 \(mx^2+cx\) 元。求能获得的美味度减去减去花费的最大值。(\(1\le n\le 100,1\le a_i\le 10^3,m\in\{0,1\},|d_{i,j}|\le 500\))

\(n\) 真的很小,很小,再加上必须选的是一个区间的限制可以转化为 \(d_{l,r}\) 选了,则 \([l,r]\) 的所有子区间都要选,所以最大权闭合子图,求法就用网络流。

我们来总结一下存在哪些类型的有向边作为限制。

  • 收费限制,即对于每种寿司 \(i\),都要有一条有向边指向一个 互不相同 的虚点 \(x_{i,a_i}\),保证每个寿司都要收费。
  • 代号限制,即对于每种寿司 \(i\),都要有一条有向边指向一个 唯一 的虚点 \(y_{a_i}\),保证对应代号的寿司只收一次。
  • 区间限制,对于表示选区间 \([l,r]\) 的点,都要有两条有向边分别指向 \([l,r-1],[l-1,r]\)。特别地,区间 \([i,i]\) 即为第 \(i\) 种寿司对应的点。

接下来考虑每种点的权值。

  • 区间 \([l,r]\) 点,权值显然为 \(d_{l,r}\)
  • 虚点 \(y_{a_i}\),权值是 \(-m{a_i}^2\),表示收费中的 \(mx^2\) 部分。
  • 虚点 \(x_{i,a_i}\),权值是 \(-a_i\),表示收费中的 \(cx\) 部分。

然后就是套网络流求最大权闭合子图的板子了,时间复杂度是求一遍网络流的复杂度。我的代码还把 \(a_i\) 离散化了一下,不过没必要。

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
const int N = 5e6 + 10, M = 110, inf = 2e9; int a[M], b[M], rev[M], tmp[M], id[M][M], ID[M], len;
struct edge{ int v, c, next; }E[N << 1]; int cur[N], p[N], cnt, S, T = N - 1, tn, d[N];
inline void init() { memset(p, -1, sizeof (p)); memset(cur, -1, sizeof (cur)); cnt = 0; }
inline void insert(int u, int v, int c) { E[cnt].v = v; E[cnt].c = c; E[cnt].next = p[u]; p[u] = cnt++; }
inline void addedge(int u, int v, int c) { insert(u, v, c); insert(v, u, 0); } 
inline bool bfs()
{
    memset(d, -1, sizeof (int) * (tn + 10)); d[T] = -1;
    std::queue<int> q; q.push(S); d[S] = 0; cur[S] = p[S];
    while (!q.empty())
    {
        int u = q.front(); q.pop();
        for (int i = p[u], v; i + 1; i = E[i].next)
        {
            v = E[i].v; cur[v] = p[v];
            if (d[v] == -1 && E[i].c) d[v] = d[u] + 1, q.push(v);
        }
    }
    return d[T] != -1;
}
int dfs(int u, int flow)
{
    if (u == T) return flow; int ans = 0, ret;
    for (int i = cur[u], v; i + 1; i = E[i].next)
    {
        v = E[i].v; cur[u] = i;
        if (d[v] == d[u] + 1 && E[i].c)
        {
            ret = dfs(v, std::min(E[i].c, flow));
            E[i].c -= ret; E[i ^ 1].c += ret; 
            flow -= ret; ans += ret; if (!flow) break;
        }
    }
    if (!ans) d[u] = -1; return ans;
}
int dinic() { int ans = 0; while (bfs()) ans += dfs(S, inf); return ans; }
int main()
{
    init(); int n, m, ans = 0; scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), tmp[i] = a[i];
    std::sort(tmp + 1, tmp + 1 + n); len = std::unique(tmp + 1, tmp + 1 + n) - tmp - 1;
    for (int i = 1; i <= n; ++i) b[i] = std::lower_bound(tmp + 1, tmp + 1 + len, a[i]) - tmp, rev[b[i]] = a[i];
    for (int i = 1; i <= len; ++i) addedge(++tn, T, m * rev[i] * rev[i]);
    for (int i = 1; i <= n; ++i) addedge(ID[i] = ++tn, T, a[i]), addedge(ID[i], b[i], inf);
    for (int i = 1, d; i <= n; ++i)
        for (int j = 1; j <= n - i + 1; ++j)
        {
            scanf("%d", &d); 
            if (d < 0) addedge(!id[i][j] ? id[i][j] = ++tn : id[i][j], T, -d);
            else addedge(S, !id[i][j] ? id[i][j] = ++tn : id[i][j], d), ans += d;
            if (j == 1) addedge(id[i][j], ID[i], inf);
            else
            {
                addedge(id[i][j], id[i][j - 1], inf); 
                addedge(id[i][j], (!id[i + 1][j - 1] ? (id[i + 1][j - 1] = ++tn) : id[i + 1][j - 1]), inf);
            }
        }
    printf("%d\n", ans - dinic()); return 0;
}
posted @ 2022-03-16 21:39  zhiyangfan  阅读(57)  评论(0编辑  收藏  举报