Logo FiraCode 的博客

博客

标签
暂无

CF1242C题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-05-02 21:39:39

题解思路:

无解情况:

所有的价值和不是 $k$ 的倍数那么他就无解。

我们发现每个合法都是一个环。

那么我们就可以用二进制表示。

然后我们就看这些二进制能否构成全一串就可以了。

具体怎么做:

首先判断是否有解,即他们的价值和 $\mod k = 0$。

预处理环。

状压 dp $\longrightarrow$ 二进制全 $1$。

AC CODE:

#include <cstring>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;
typedef long long ll;
ll sum[15];
vector <int> a[15];
map <ll , pair <int , int>> mp;
bool vis[1 << 15] , dp[1 << 15];
vector <pair <pair<int , int> , int>> mark[1 << 15];
int pre[1 << 15];
pair <int , int> ans[15];
int main() {
    int k;
    scanf ("%d" , &k);
    ll v = 0;
    for (int i = 0; i < k; ++ i) {
        int n;
        scanf ("%d" , &n);
        a[i].resize (n);
        for (int j = 0; j < n; ++ j) {
            scanf ("%d" , &a[i][j]);
            sum[i] += a[i][j];\/\/sum[i]第i组的和
            mp[a[i][j]] = {i , j};\/\/i组j个
        }
        v += sum[i];\/\/总和 
    }
    if (v % k != 0) return puts("No") , 0;\/\/无解
    v \/= k;\/\/分成k个小段,每段的和
    for (int i = 0; i < k; ++ i) {\/\/预处理环
        for (int j = 0; j < a[i].size(); ++ j) {
            ll cur = a[i][j];\/\/枚举a[i][j],复杂度15×5000
            int now = 0;\/\/现在的状态
            bool ok = true;
            vector <pair<pair <int , int> , int>>b;
            while (1) {
                int y = mp[cur].first;\/\/用map记录
                cur = v - sum[y] + cur;\/\/他对应的值:sum[i]+a[i][j]+?=v,我们已知:sum[i],v,
                if (!mp.count(cur)) {
                    ok = false;
                    break;
                }
                auto x = mp[cur];
                if (now & (1 << x.first)) {
                    ok = false;
                    break;
                }
                now |= (1 << x.first);
                b.push_back ({{x.first , x.second} , y});
                if (x.first == i) {\/\/一直往前找,又回到了期点,有环
                    if (x.second != j) ok = false;
                    break;
                }
            }
            if (ok) {
                vis[now] = true;
                mark[now] = b;
            }
        }
    }
    dp[0] = 1;
    for (int s = 0; s < (1 << k); ++ s) {\/\/枚举子集,状压DP
        for (int ns = s; ns; ns = (ns - 1) & s)
            if (vis[ns] && dp[s ^ ns]) {
                dp[s] = 1;
                pre[s] = ns;
                break;
            }
    }
    if (!dp[(1 << k) - 1]) return puts("No") , 0;\/\/没有全1那么也无解
    int s = 1 << k;
    -- s;
    while (s) {
        for (auto &x : mark[pre[s]]) {
            ans[x.first.first] = {a[x.first.first][x.first.second] , x.second};
        }
        s ^= pre[s];
    }
    puts("Yes");
    for (int i = 0; i < k; ++ i)
        printf ("%d %d\n" , ans[i].first , ans[i].second + 1);
    return 0;
}

码字不易,求赞!

CF1345B题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-05-05 20:38:14

题解思路:

我们发现:

当搭高度为 $1$ 时,那么他就只用两个牌。

当塔高度为 $i$ 时,那么他就是把第 $i - 1$ 个封口在加上 $i$ 个尖,如图:

就的到了一下公式:($f_{i}$ 代表高为 $i$ 的金字塔的牌的数量)

$\left\{\begin{matrix} f_{1} = 2\ f_{i} = f_{i - 1} + i - 1 + 2 \times i \end{matrix}\right.$

然后根据题意二分求出第一个 $\le$ 牌数的 $f$ 值,然后减去就可以了!

AC CODE:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
int T;
int cnt;
int f[100010];
void init () {
	f[1] = 2;
	for (cnt = 2; f[cnt - 1] <= 1000000000; ++ cnt)
		f[cnt] = f[cnt - 1] + cnt - 1 + 2 * cnt;\/\/预处理搭建i高的金字塔的纸牌数 
}
int main() {
	init ();
	-- cnt;\/\/因为是第一个>1000000000的才会跳出,所以,cnt要-- 
	scanf ("%d" , &T);
	while (T --) {
		int n;
		scanf ("%d" , &n);
		int ans = 0;
		int mid = 0;
		do {
			int l = 1 , r = cnt;
			while (l <= r) {\/\/二分
				mid = (l + r) >> 1;
				if (f[mid] == n) break;\/\/找到了直接退出 
				else if(f[mid] < n) l = mid + 1;
				else r = mid - 1;
			}
			if (f[mid] > n) mid --;\/\/若比他大了就-- 
			if (mid <= 0) break; \/\/若mid小于等于0就是没有小于等于n的了 
			n -= f[mid];\/\/减去用的纸牌数 
			ans ++;\/\/搭了一个 
		}while (true);
		printf ("%d\n" , ans);
	}
	return 0;
}

码字不易,求赞!

CF1234B2题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-05-11 19:42:38

题解思路:

deque 来维护序列,然后,用一个 map 来看是否添加过了。

具体分为一下步骤:

若当前已经有了,那么直接 continue

若长度 $= k$ 了,就弹出队尾,然后把标记删掉。

然后插入队头,然后再标记一下就可以了。

AC CODE:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <deque>
#include <map>
using namespace std;
int n , k;
int a[200010];
deque <int> q;\/\/维护序列
map <int , int> ma;\/\/在聊天框里是否出现过
int main(){
	scanf ("%d%d" , &n , &k);
	for (int i = 1; i <= n; ++ i)
		scanf ("%d" , &a[i]);
	for (int i = 1; i <= n; ++ i) {
		if (ma[a[i]]) continue;\/\/已经出现过了,那么就跳过
		if (q.size () == k) ma[q.back()] = false , q.pop_back();\/\/弹出,然后去掉标记
		if (!ma[a[i]])q.push_front (a[i]) , ma[a[i]] = true;\/\/插入,标记
	}
	cout << q.size() << endl;\/\/输出长度
	for (int i = 0; i < q.size(); ++ i)
		cout << q[i] << ' ';\/\/输出聊天窗里的数
	return 0;\/\/完结散花
}

码字不易,求赞!

CF1630B题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-05-14 11:01:37

题解思路:

假设我们有若干个长为 $n$ 的数组。

有 $len$ 个满足条件 $A$ 的数字。

我们至少要分成 $k$ 组。

每组都要满足 $A$ 的数的个数 $>$ 不满足 $A$ 的个数。

那么每组若只多一个,那么 $n$ 里面满足条件 $A$ 的至少要比不满足的多 $k$ 个,

所以一组里不满足的就有 $\left \lfloor \frac{(n-k)}{2} \right \rfloor$ 个。

所以满足的条件就是 $n - \left \lfloor \frac{(n-k)}{2} \right \rfloor$。

你选的数 $len = \left \lceil \frac{(n-k)}{2} \right \rceil = \left \lfloor \frac{(n-k + 1)}{2} \right \rfloor$

那么你的值域区间就是 $\left[ a_i,a_{i+len-1}\right]$。

排完序之后,遍历一下,你就能知道他的最小的值域区间是多少。

但是知道了最小的值域我们还要构造一个区间的分开方式

我们回到原序列,若有一个在所选区间的数,就让计数器 $+1$。

若不在值域的数,那么计数器 $-1$。

那么计数器 $>1$ 的区间,就把左右端点记下来,若最后的数字没有分完就直接把他移到右端点就可以了。

最后直接输出左右端点就可以了。

AC CODE:

#include <algorithm>
#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;
const int N = 200010;
int n;
int cnt;
int tot , k;
int a[N] , b[N];
int l[N] , r[N];
int ansl , ansr;
vector <int> p[N];
void solve() {
	scanf ("%d%d" , &n , &k);
	tot = 0;
	int len = (n + k + 1) \/ 2;\/\/计算一个区间的长度
	for (int i = 1; i <= n; i++) {
		scanf ("%d" , &a[i]);
		b[i] = a[i];\/\/b保存的是排序后的数组
	}
	sort(b + 1, b + 1 + n);\/\/排序
	ansl = 1, ansr = len;
	for (int i = 2; i + len - 1 <= n; i++) 
		if (b[i + len - 1] - b[i] < b[ansr] - b[ansl]) 
			ansl = i, ansr = i + len - 1;
	for (int i = 1, u = 1; i <= k; u++, i++) {
		l[i] = u;
		for (int cnt = 0;; u++) {
			if (a[u] >= b[ansl] && a[u] <= b[ansr]) \/\/若满足条件
				cnt++;\/\/计数器++
			else \/\/否则
				cnt--;\/\/计数器--
			if (cnt > 0) {\/\/只要大于0,就是
				r[i] = u;
				break;
			}
		}
	}
	r[k] = n;
	printf ("%d %d\n" , b[ansl] , b[ansr]);
	for (int i = 1; i <= k; i++) \/\/输出
		printf ("%d %d\n" , l[i] , r[i]);
}
int main() {
	int T;
	scanf ("%d" , &T);
	while (T --)
		solve();
	return 0;
}

码字不易,求赞!

CF822B题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-05-18 20:41:25

题解思路:

枚举 $t$ 的每一个字母作为开头,然后再和 $s$ 比较,记录一下哪里不一样就要改,最后对这些修改的序列取长度最小的就可以了,长度一样的话,随便一个就可以了。

AC Code

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdio>
#include <vector>
#include <string>
using namespace std;
int n , m;
string a , b;
int main(){
	\/\/freopen ("temp.in" , "r" , stdin);
	scanf ("%d%d" , &n , &m);
	cin >> a >> b;
	vector <int> ans(114514 , 1);\/\/因为要取min,所以先把长度设成很大的值。
	for (int i = 0; i < m - n + 1; ++ i) {\/\/枚举每一个t里的字母
		vector <int> temp;
		for (int j = 0; j < n; ++ j) 
			if (a[j] != b[i + j]) \/\/只要不一样就要是 ?
				temp.push_back (j);\/\/那么操作就加上j
		if (temp.size() < ans.size()) \/\/更新答案
			ans = temp;
	}
	printf ("%d\n" , ans.size());
	for (int i = 0; i < ans.size(); ++ i)\/\/输出
		printf ("%d " , ans[i] + 1);
	puts("");
	return 0;
}

CF12E题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-05-23 22:20:57

题解思路

首先构造一个序列,他满足第二个条件:

1 2 3 4 5
2 3 4 5 1
3 4 5 1 2
4 5 1 2 3
5 1 2 3 4

然后我们把中间换成 $0$:

0 2 3 4 5
2 0 4 5 1
3 4 0 1 2
4 5 1 0 3
5 1 2 3 0

然后再把变成 $0$ 的给添到后面:

0 2 3 4 5 1
2 0 4 5 1 3
3 4 0 1 2 5
4 5 1 0 3 2
5 1 2 3 0 4
1 3 5 2 4 0

然后就可以了!

AC CODE:

#include 
#include 
using namespace std;
const int N = 1010;
int n;
int a[N][N];
int main() {
    cin >> n;
    for (int i = 0; i < n; ++ i)
      for (int j = 0; j < n; ++ j)
	        a[i][j] = (i + j) % (n - 1) + 1;\/\/处理一个对称矩阵
    for (int i = 0; i < n; ++ i) {
	      a[i][n - 1] = a[n - 1][i] = a[i][i];\/\/把他填在后面
	      a[i][i] = 0;\/\/替换成0
    }
    for (int i = 0; i < n; ++ i) {
    	  for (int j = 0; j < n; ++ j)
  	  	    cout << a[i][j] << ' ';
  	  	cout << endl;
    }
    return 0;
}

CF311B题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-06-19 15:58:41

题解思路:

转化题意:

我们先求一下从第一座山到以后的每一座山的距离先求出来,也就是对 $d_i$ 求一个前缀和。

那么现在的 $d_i$ 就表示 $1$~$i$ 的距离。

一个饲养员走到一个猫的山开始的时间为 $s_i$。

那么若 $s_i + d_i \ge ti$ 那么饲养员才能把第 $i$ 只小猫接上。

那么这只小猫的等待时间为: $$s_i+\Delta t - (t_i-d_i)$$

那么我们用 $a_i$ 来表示 $t_i-a_i$。

那么 $a_i$ 就是要接走这只猫的最早出发时间。

那么我们把小猫的 $a_i$ 从小到大排序。

那么饲养员出发的时候,每一次饲养员每次都要接走一批的小猫。

若一个饲养员要接走前 $i$ 只小猫,那么就要在 $a_i$ 时刻出发,当然这里的 $a_i$ 是排序后的值。

那么前 $i$ 等待的时间之和就是: $$a_i-a_i+a_i-a_{i-1}+...+a_i-a_l$$ $$=a_i*(i-l+1)-(Sum_i-Sum_{l-1})$$ 其中 $l$ 表示这段区间的左端点,$i$ 就是右端点,$Sum$ 就是 $a_i$ 的前缀和。

那么就相当于有 $m$ 个数,然后我们给他分成 $p$ 组,然后每组的最后一个减去其他的差的和的最小值。

这样的话就转化成了和这道题类似了。

然后我们用斜率优化DP来做就可以了。

解法:

那么我们设 $f_{j,i}$ 表示:前 $i$ 只小猫分成 $j$ 组的最小等待时间也就是用 $j$ 个饲养员。

状态转移方程: $$f_{j,i} = \max\{f_{j-1,k} + a_i \times (i-k) - (Sum_i-Sum_k)\}$$

我们把 $i$ 和 $j$ 看作常量,那么变形: $$f_{j,i} = f_{j - 1 , k} - a_i \times k + Sum_k + a_i \times - Sum_i$$

那么和 $k$ 有关的变量有: $$f_{j-1,k},k,Sum_k$$

整理一下: $$f_{j-1,k} + Sum_k = a_i \times k + f_{j,i} - a_i\times i + Sum_i$$

因为我们对 $a_i$ 排序了,所以 $a_i$ 是单调递增的。

那么 $f_{j,i} - a_i \times i + Sum_i$ 就是截距。

那么 $k$ 就是 $x$,$f_{j-1,k} + Sum_k$ 是 $y$。

时间复杂度为 $\mathcal{O}(pm)$。

十年OI一场空,不开long long见祖宗!

AC CODE

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 100010 , M = 100010 , P = 110;
int n , m , p;
ll d[N] , t[N] , a[N] , s[N];
ll f[P][M];
int q[M];
ll get_y(int k , int j) {
	return f[j - 1][k] + s[k];
}
int main(){
    scanf ("%d%d%d" , &n , &m , &p);
	for (int i = 2; i <= n; ++ i){
	    scanf ("%lld" , &d[i]);
		d[i] += d[i - 1];\/\/预处理前缀和。
	}
	for (int i = 1; i <= m; ++ i){
		int h;
		scanf ("%d%lld" , &h , &t[i]);
		a[i] = t[i] - d[h];\/\/a数组
	}
	sort (a + 1 , a + 1 + m);\/\/排序
	for (int i = 1; i <= m; ++ i) s[i] += s[i - 1] + a[i];\/\/a的前缀和
	memset (f , 0x3f , sizeof (f));
	for (int i = 0; i <= p; ++ i) f[i][0] = 0;
	for (int j = 1; j <= p; ++ j) {\/\/DP
		int hh = 0 , tt = 0;
		q[0] = 0;
		for (int i = 1; i <= m; ++ i) {
			while (hh < tt && (get_y(q[hh + 1] , j) - get_y(q[hh] , j)) <= a[i] * (q[hh + 1] - q[hh])) ++ hh;
			int k = q[hh];
			f[j][i] = f[j - 1][k] - a[i] * k + s[k] + a[i] * i - s[i];
			while (hh < tt && (get_y(q[tt] , j) - get_y(q[tt - 1] , j)) * (i - q[tt]) >= (get_y(i , j) - get_y(q[tt] , j)) * (q[tt] - q[tt - 1])) -- tt;
			q[++ tt] = i;
		}
	}
	printf ("%lld\n" , f[p][m]);
	return 0;
}

CF1527E题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-07-15 12:10:27

题解思路:

我们可以用 dp,状态转移为: $$dp_{i , j} = \min\{dp_{k , j - 1} + w_{k + 1 , i}\}$$ 但时间复杂度至少为 $\mathcal{O(n^2k)}$。 我们怎么用数据结构去优化呢?

  1. 我们就按 $j$ 分层。 每次从 $j - 1$ 的状态推出 $j$。 则 $f_i=dp_{i , j - 1}$。 而 $g_i = \min(f_k + w_{k + 1 , i})$。
  2. 怎么维护: 设 $h_k = f_k + w_{k + 1 , i}$。 当 $i \to i + 1$。 分两种情况:

1.当前数没有出现过,则贡献为 $0$。 2.若他出现过,那么他的贡献就是加上了 $i + 1 -$ 上一次出现的位置。

这就是前缀加以及前缀最小值。

那么我们用线段树来维护 $h$ 数组即可。

AC CODE:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef int ll;
const int N = 35100;
int n , k;
int last[N] , pre[N] , f[N];
struct node {
	ll t;
	ll val;
}seg[N * 4];
int a[N];
void update (int id) {
	seg[id].val = min(seg[id * 2].val , seg[id * 2 + 1].val);
}
void settag (int id , int t) {
	seg[id].val = seg[id].val + t;
	seg[id].t = seg[id].t + t;
}
void pushdown (int id) {
	if (seg[id].t != 0) {\/\/标记非空
		settag (id * 2 , seg[id].t);
		settag (id * 2 + 1 , seg[id].t);
		seg[id].t = 0;
	}
}
void build (int id , int l , int r) {
	seg[id].t = 0;
	if (l == r) {
		seg[id].val = f[l];
	}else {
		int mid = (l + r) >> 1;
		build (id * 2 , l , mid);
		build (id * 2 + 1 , mid + 1 , r);
		update (id);
	}
}
void modify (int id , int l , int r , int ql , int qr , int t) {
	if (l == ql && r == qr){ settag(id , t); return;}
	int mid = (l + r) \/ 2;
	pushdown (id);
	if (qr <= mid) modify (id * 2 , l , mid , ql , qr , t);
	else if (ql > mid) modify (id * 2 + 1 , mid + 1 , r , ql , qr , t);
	else {
		modify (id * 2 , l , mid , ql , mid , t);
		modify (id * 2 + 1 , mid + 1 , r, mid + 1 , qr ,t);
	}
	update (id);
}
ll query (int id , int l , int r , int ql , int qr) {
	if (l == ql && r == qr) return seg[id].val;
	int mid = (l + r) \/ 2;
	pushdown (id);
	if (qr <= mid) return query (id * 2 , l , mid , ql , qr);
	else if (ql > mid) return query (id * 2 + 1 , mid + 1 , r , ql , qr);
	else {
		return min(query (id * 2 , l , mid , ql , mid) , query (id * 2 + 1 , mid + 1 , r, mid + 1 , qr));
	}
}
int main() {
	scanf ("%d%d" , &n , &k);
	for (int i = 1; i <= n; ++ i) {
		scanf ("%d" , &a[i]);
		last[i] = pre[a[i]];
		pre[a[i]] = i;
	}
	f[0] = 0;
	for (int i = 1; i <= n; ++ i) f[i] = 1 << 30;
	for (int j = 1; j <= k; ++ j) {
		build (1 , 0 , n);
		for (int i = 1; i <= n; ++ i) {
			if (last[i] != 0) modify (1 , 0 , n , 0 , last[i] - 1 , i - last[i]);
			f[i] = query (1 , 0 , n , 0 , i - 1);
		}
	}	
	printf ("%d\n" , f[n]);
	return 0;
}

CF1644D题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-07-23 09:40:20

题解思路:

我们就倒着考虑,那么题目就变成了:

每次选一行一列,然后染成一个颜色,后染的色不会覆盖原来染得颜色。

那么当一个颜色会有颜色,当且仅当他是满足没有全覆盖并且行没有完全覆盖或列没有完全覆盖;

那么我们定义两个数组一个表示当前行是否完全覆盖,另一个表示当前列是否完全覆盖。

那么我们就顺着做一遍,在用两个变量分别表示覆盖了多少行和覆盖了多少列。

那么只要行数等于 $n$ 了或者列数等于 $m$ 了就退出或者跳过。

十年OI一场空,不开longlong见祖宗。

AC CODE:

#include <bits\/stdc++.h>
using namespace std;
const int N = 1000005, mod = 998244353;
int n, m, k, q;
int x[N], y[N];
int fn[N], fm[N];
int main()
{
	int T = 1;
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d%d%d%d", &n, &m, &k, &q);
		for (int i = 1; i <= q; i++)
			scanf("%d%d", &x[i], &y[i]);
		int cn = 0, cm = 0;
		long long res = 1;
		for (int i = q; i >= 1; i--)
		{
			if (cn == n || cm == m)\/\/若全覆盖就跳过,但最好直接跳出
				continue;
			int fl = 1;
			if (!fn[x[i]])\/\/若没有覆盖就覆盖
				cn++, fn[x[i]] = 1, fl = k;
			if (!fm[y[i]])\/\/同理
				cm++, fm[y[i]] = 1, fl = k;
			res = res * fl % mod;\/\/统计答案
		}
		for (int i = q; i >= 1; i--)
			fn[x[i]] = fm[y[i]] = 0;\/\/多组数据清空
		printf("%d\n", res);
	}
	return 0;
}

CF1637D题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-04-11 17:37:06

题意:

有 $A,B$ 两个序列,长度为 $n$。 定义一个序列 $a_1 , a_2 ... a_n$。

他的价值: 为 $\sum^{n}{i=1} \sum^{n}{j=i+1} (a_i + a_j) ^ 2$

我们可以交换 $a_i,b_i$ 的位置。

最后求 $A$ 的价值 $+$ $B$ 的价值最小。

题解思路:

先把计算价值的式子拆一下变成:

$k_1 (\sum^{n}{i=1}a_i ^ 2) + k_2 (\sum^{n}{i=1} \sum^{n}_{j=i+1}a_i \times a_j)$

已知 $k_1 = n - 1 , k_2 = 2$
$=(n-1)(\sum^{n}{i=1}a_i ^ 2) + 2(\sum^{n}{i=1} \sum^{n}_{j=i+1}a_i \times a_j)$

我们可以发现,不论我们怎么改,$a_i$ 乘方的次数是不变的。

但我们能改变 $a_i \times a_j$ 的次数

我们定义 $S_a$ 为他们两两想乘的结果

设一个序列为:$(a_i , a_2 , a_3)$

$S_{(a_1,a_2,a_3)}$ $\Longrightarrow$ $S_{(a_1,a_2,a_3,a_4)}$ 他们有什么不同的地方呢?

其实他就是加上了 $a_4 (a_1 + a_2 + a_3)$

所以,我们就可以进行 DP 了。

因为我们只需要存 $a_1 + a_2 + a_3 ... + a_n$ 的 $sum$。

$dp_{sumA,sumB,i}$ 表示分配了 $a_1 ... a_i$,$b_1 ... b_i$ 为 $S_{(a_1 ... a_i)} + S_{(b_1 ... b_i)}$($S$ 之前定义过了)。

就是当 $a_1 + a_2 + ... + a_i = sumA$ 并且 $b_1 + b_2 + ... + b_i = sumB$ 并且分配了前 $i$ 情况下 $S$ 的最小值。

当我们给 $a$ 序列增加一个 $x$,给 $b$ 序列增加一个 $y$ 时,$dp_{sumA,sumB}$ 其实就是加上 $x \times sumA , y \times sumB$。

实际上 $sumB$ 这一维是不需要的,因为我们有了 $dp_i$ 了,那么我们就知道了 $sumA + sumB$,我们就可以直接算出 $sumB$ 的值了。

其实 $dp_{sumA,sumB,i}$ 有很多是无意义的。

就比如 $sumA + sumB \ne (a_1 + a_2 + ... + a_n + b_1 + b_2 + ... + b_n)$,那么 $dp_{sumA , sumB , i}$ 就是无意义的。

那我们就删掉一维,因为 $sumA$ 只有一个唯一对应的一个 $sumB$,我们就把 $sumB$ 这一维压掉。

$O(n ^ 3)$ 个状态,转移时间为 $O(1)$,所以总时间复杂度为 $O(n ^ 3)$。

【AC 记录】

AC Code:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110 , M = 20010;
const long long INF = 1e15;
int n;
int a[N] , b[N];
long long dp[M] , tmpdp[M];
int main() {
	int T;
	cin >> T;
	while (T --) {
		scanf("%d", &n);
		for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
		for (int i = 1; i <= n; i++) scanf("%d", &b[i]);
		for (int i = 0; i <= 100 * n; ++ i) dp[i] = tmpdp[i] = INF;
		tmpdp[0] = 0;
		int Sum = 0;
		for (int i = 1; i <= n; i++) {
			for (int j = 0; j <= 100 * n; j++) {
				int tmp1 = j, tmp2 = Sum - j;\/\/算出sumA,sumB 
				if (tmpdp[j] == INF) continue;
				dp[j + a[i]] = min(dp[j + a[i]], tmpdp[j] + a[i] * tmp1 + b[i] * tmp2);\/\/用了滚数组优化 
				dp[j + b[i]] = min(dp[j + b[i]], tmpdp[j] + a[i] * tmp2 + b[i] * tmp1);
			}
			Sum += a[i] + b[i];
			for (int j = 0; j <= 100 * n; j++) {
				tmpdp[j] = dp[j];
				dp[j] = INF;
			}
		}
		long long sum1 = INF, sum2 = 0;
		for (int i = 0; i <= 100 * n; i++) sum1 = min(sum1, tmpdp[i]);\/\/看DP[sumA]最小的是多少
		for (int i = 0; i <= n; i++) sum2 += (a[i] * a[i] + b[i] * b[i]);\/\/就是平方的那个 
		printf("%lld\n", sum2 * (n - 1) + sum1 * 2);\/\/输出答案 
	}
	return 0;
}
共 105 篇博客