若干思维题总结(更新ing)

P6005 [USACO20JAN] Time is Mooney G

题目描述

Bessie 正在安排前往牛尼亚的一次出差,那里有 \(N\)\(2 \leq N \leq 1000\))个编号为 \(1 \ldots N\) 的城市,由 \(M\)\(1 \leq M \leq 2000\))条单向的道路连接。Bessie 每次访问城市 \(i\) 都可以赚到 \(m_i\) 哞尼(\(0 \leq m_i \leq 1000\))。从城市 \(1\) 出发,Bessie 想要赚到尽可能多的哞尼,最后回到城市 \(1\)。为了避免争议,\(m_1=0\)

沿着两个城市之间的道路移动需要消耗一天。出差的准备工作十分费钱;旅行 \(T\) 天需要花费 \(C \times T^2\) 哞尼(\(1 \leq C \leq 1000\))。

Bessie 在一次出差中最多可以赚到多少哞尼?注意有可能最优方案是 Bessie 不访问城市 \(1\) 之外的任何城市,在这种情况下结果应当为 \(0\)

输入格式

输入的第一行包含三个整数 \(N\)\(M\)\(C\)

第二行包含 \(N\) 个整数 \(m_1,m_2,\ldots, m_N\)

以下 \(M\) 行每行包含两个空格分隔的整数 \(a\)\(b\)\(a \neq b\)),表示从城市 \(a\) 到城市 \(b\) 的一条单向道路。

输出格式

输出一行,包含所求的答案。

输入输出样例 #1

输入 #1

3 3 1
0 10 20
1 2
2 3
3 1

输出 #1

24

说明/提示

最优的旅行方案是 \(1 \to 2 \to 3 \to 1 \to 2 \to 3 \to1\)。Bessie 总共赚到了 \(10+20+10+20-1 \times 6^2=24\) 哞尼。

题解

考虑DP
\(f_{i,j}\)为第\(i\)天到达城市\(j\)的最大收益
那么很显然,
转移方程为\(f_{i,j}=max(f{i-1,k})\),其中\(k\)为与城市\(j\)相邻的城市
\(i\)的范围并没有给出
那我们便可以手动推一下
因为收益不能为负
因此可得式子\(-c\times i^2 + max(m_i) \times i >= 0\)
我们令\(c\)取最小值\(1\)\(max(m_i)\)取最大值\(1000\)
\(-i^2+1000i?=0\)
\(i(1000-i)>=0\)
可得\(i \le 1000\)
那么就可以愉快的AC这道题了

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 7, inf = 0x3f3f3f3f;
struct edge{
	int to, pre;
}e[N << 1];
struct node{
	int u, w, t;
	bool operator<(const node &a)const{
		return w < a.w;
	} 
};
int n, m, c;
int a[N];
int head[N], tot;
int f[N][N], ans;
void add(int x, int y){
	e[++tot] = {y, head[x]};
	head[x] = tot;
}
int main(){
	scanf("%d%d%d", &n, &m, &c);
	for(int i = 1; i <= n; i++){
		scanf("%d", a + i);
	} 
	for(int i = 1; i <= m; i++){
		int x, y;
		scanf("%d%d", &x, &y);
		add(y, x);
	}
	memset(f, -1, sizeof(f));
	f[0][1] = 0;
	for(int i = 1; i <= 1000; i++){
		for(int j = 1; j <= n; j++){
			for(int k = head[j]; k; k = e[k].pre){
				int v = e[k].to;
				if(f[i - 1][v] != -1){
					f[i][j] = max(f[i][j], f[i - 1][v] + a[j]);
				}
			}
		}
		ans = max(ans, f[i][1] - c * i * i);
	}
	printf("%d\n", ans);
	return 0;
}

P8186 [USACO22FEB] Redistributing Gifts S

题目描述

FJ 有 \(N\) 个礼物给他的 \(N\) 头奶牛,这 \(N\) 个礼物和 \(N\) 头奶牛都分别按顺序被标记为从 \(1\)\(N\) 的整数。每头奶牛都有一个愿望单,记录着一个含有 \(N\) 个礼物的排列。比起在愿望单中出现更晚的礼物,奶牛更喜欢先出现在愿望单中的礼物。

因为 FJ 太懒了,他直接把 \(i\) 号礼物分配给了 \(i\) 号奶牛。现在,奶牛们聚在了一起,决定重新分配礼物,以便在重新分配后,每头奶牛都能得到跟原来一样,或是它更喜欢的礼物。

对于每个 \(i\)\(1\le i\le N\)),计算出重新分配后, \(i\) 号奶牛可能拿到的最好的礼物(这个奶牛经过重新分配后能拿到的最喜欢的礼物)。

输入格式

第一行输入 \(N\)。之后 \(N\) 行每行包含一个奶牛的愿望单。保证这 \(N\) 行都是从 \(1\)\(N\) 的排列,即每一行都不按顺序不重不漏地包含 \(1\)\(N\) 中的每个整数。

输出格式

输出 \(N\) 行,第 \(i\) 行输出重新分配后 \(i\) 号奶牛可能得到的最好礼物。

输入输出样例 #1

输入 #1

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

输出 #1

1
3
2
4

说明/提示

【样例解释】

在这个例子中,有两种可能的分配方案:

  • 最初的方案:一号奶牛得到一号礼物,二号奶牛得到二号礼物,三号奶牛得到三号礼物,四号奶牛得到四号礼物。
  • 重新分配后的方案:一号奶牛得到一号礼物,二号奶牛得到三号礼物,三号奶牛得到二号礼物,四号奶牛得到四号礼物。可以发现一号和四号奶牛都拿不到比 FJ 分配的更好的礼物,不过二号和三号都可以。

【数据范围】

\(2 \sim 3\) 号测试点满足 \(N \le 8\)

对于 \(100\%\) 的数据,满足 \(1 \le N \le 500\)

题解:

考虑图论建模
\(f_{i,j}\)表示第\(i\)头奶牛是否可以与第\(j\)头奶牛交换礼物
那么这显然是一个传递闭包问题
便可以用\(Floyd\)解决

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 507;
int n;
int a[N][N];
int f[N][N];
int main(){
	scanf("%d", &n);
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			scanf("%d", &a[i][j]);
		}
	}
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			f[i][a[i][j]] = 1;//初始化
			if(a[i][j] == i) break;
		}
	}
	for(int k = 1; k <= n; k++){
		for(int i = 1; i <= n; i++){
			for(int j = 1; j <= n; j++){
				f[i][j] |= f[i][k] && f[k][j];
			}
		}
	}
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			if(f[i][a[i][j]] && f[a[i][j]][i]){
				printf("%d\n", a[i][j]);
				break;
			}
		}
	}
	return 0;
}

P7149 [USACO20DEC] Rectangular Pasture S

题目描述

Farmer John 最大的牧草地可以被看作是一个由方格组成的巨大的二维方阵(想象一个巨大的棋盘)。现在,有 \(N\) 头奶牛正占据某些方格(\(1≤N≤2500\))。

Farmer John 想要建造一个可以包围一块矩形区域的栅栏;这个矩形必须四边与 \(x\) 轴和 \(y\) 轴平行,最少包含一个方格。请帮助他求出他可以包围在这样的区域内的不同的奶牛子集的数量。注意空集应当被计算为答案之一。

输入格式

输入的第一行包含一个整数 \(N\)。以下 \(N\) 行每行包含两个空格分隔的整数,表示一头奶牛所在方格的坐标 \((x,y)\)。所有 \(x\) 坐标各不相同,所有 \(y\) 坐标各不相同。所有 \(x\)\(y\) 的值均在 \(0…10^9\) 范围内。

输出格式

输出 FJ 可以包围的奶牛的子集数量。可以证明这个数量可以用 64 位有符号整数型存储(例如 C/C++ 中的long long)。

输入输出样例 #1

输入 #1

4
0 2
1 0
2 3
3 5

输出 #1

13

说明/提示

共有 \(2^4\) 个子集。FJ 不能建造一个栅栏仅包围奶牛 \(1\)\(2\)\(4\),或仅包围奶牛 \(2\)\(4\),或仅包围奶牛 \(1\)\(4\),所以答案为 \(2^4-3=16-3=13\)

  • 测试点 2-3 满足 \(N≤20\)
  • 测试点 4-6 满足 \(N≤100\)
  • 测试点 7-12 满足 \(N≤500\)
  • 测试点 13-20 没有额外限制。

题解

考虑枚举栅栏的上下界
先以\(x\)坐标为第一关键字,\(y\)坐标为第二关键字为每个奶牛排序
随后暴力枚举\(i,j\)分别为木板的上、下界
那么木板的左右界数量便是对答案的贡献
考虑分类讨论
\(l_{i,j} r_{i,j}\)为奶牛\(i\)到奶牛\(j\)之间在奶牛\(i\)左/右侧的奶牛的数量
1.当\(x_i<x_j\)
则对答案的贡献为\(l_{i,j} \times r_{j,i}\)
2.当\(x_i>x_j\)
则对答案的贡献为\(r_{i,j} \times l_{j,i}\)
在统计答案的过程中处理出这2个量就可以了
Tips:空集也是答案

Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2507;
struct point{
	int x, y;
}a[N];
int n;
ll ans;
int l[N], r[N];
bool cmp1(point a, point b){
	if(a.x != b.x) return a.x < b.x;
	else return a.y < b.y;
} 
int main(){
	scanf("%d", &n);
	for(int i = 1; i <= n; i++){
		scanf("%d%d", &a[i].x, &a[i].y);
	}
	sort(a + 1, a + n + 1, cmp1);
	for(int i = 1; i <= n; i++){
		ans++;
		int cntl = 0, cntr = 0;
		for(int j = i - 1; j; j--){
			if(a[i].y > a[j].y){
				ans += 1ll * (l[j] + 1) * (cntr + 1);
				r[j]++;
				cntl++;
			}
			else{
				ans += 1ll * (r[j] + 1) * (cntl + 1);
				l[j]++;
				cntr++;
			}
		}
	}
	printf("%lld\n", ans + 1);//空集
	return 0;
}

P7150 [USACO20DEC] Stuck in a Rut S

题目描述

Farmer John 最近扩大了他的农场,从奶牛们的角度看来这个农场相当于是无限大了!奶牛们将农场上放牧的区域想作是一个由正方形方格组成的无限大二维方阵,每个方格中均有美味的草(将每个方格看作是棋盘上的一个方格)。Farmer John 的 \(N\) 头奶牛(\(1≤N≤1000\))初始时位于不同的方格中,一部分朝向北面,一部分朝向东面。

每一小时,每头奶牛会执行以下二者之一:

  • 如果她当前所在的方格里的草已经被其他奶牛吃掉了,则她会停下(并从这个时刻开始一直保持停止)。
  • 吃完她当前所在的方格中的所有草,并向她朝向的方向移动一个方格。

经过一段时间,每头奶牛的身后会留下一条被啃秃了的轨迹。

如果两头奶牛在一次移动中移动到了同一个有草的方格,她们会分享这个方格中的草,并在下一个小时继续沿她们朝向的方向移动。

当 Farmer John 看到停止吃草的奶牛时会不高兴,他想要知道谁该为他停止吃草的奶牛受到责备。如果奶牛 \(b\)
停在了之前奶牛 \(a\) 吃过草的一个方格,我们就称奶牛 \(a\) 阻碍了奶牛 \(b\)。进一步地,如果奶牛 \(a\) 阻碍了奶牛 \(b\) 且奶牛 \(b\) 阻碍了奶牛 \(c\),我们认为奶牛 \(a\) 也阻碍了奶牛 \(c\)(也就是说,「阻碍」关系具有传递性)。每头奶牛受到责备的程度与这头奶牛阻碍的奶牛数量一致。请计算每头奶牛受到责备的数量——也就是说,每头奶牛阻碍的奶牛数量。

输入格式

输入的第一行包含 \(N\)。以下 \(N\) 行,每行描述一头奶牛的起始位置,包含一个字符 N(表示朝向北面) 或 E(表示朝向东面),以及两个非负整数 \(x\)\(y\)\(0≤x≤10^9\)\(0≤y≤10^9\))表示方格的坐标。所有 \(x\) 坐标各不相同,所有 \(y\) 坐标各不相同。

为了使方向和坐标尽可能明确,如果一头奶牛位于方格 \((x,y)\) 并向北移动,她会到达方格 \((x,y+1)\)。如果她向东移动,她会到达方格 \((x+1,y)\)

输出格式

输出 \(N\) 行。输出的第 \(i\) 行包含输入中的第 \(i\) 头奶牛受到的责备的数量。

输入输出样例 #1

输入 #1

6
E 3 5
N 5 3
E 4 6
E 10 4
N 11 1
E 9 2

输出 #1

0
0
1
2
1
0

说明/提示

在这个样例中,奶牛 3 阻碍了奶牛 2,奶牛 4 阻碍了奶牛 5,奶牛 5 阻碍了奶牛 6。根据传递性,奶牛 4 也阻碍了奶牛 6。

  • 测试点 2-5 中,所有坐标不超过 \(2000\)
  • 测试点 6-10 没有额外限制。

题解:

观察到\(N \le 1000\)
因此可以先暴力处理出每2个奶牛是否会阻碍/被阻碍
并将所有交点储存到数组中
但有些奶牛可能会在到达这个交点之前就被其他奶牛阻拦
因为奶牛只能向右和向上走
所以一个奶牛若是无法到达某个交点
便肯定是被这个交点的左/下方的交点阻拦了
那我们就可以将交点以横坐标为第一关键字,纵坐标为第二关键字排序
若是这个交点的两只奶牛都没有被阻拦
那就计算答案
并标记被阻拦的奶牛
就可以愉快的AC这道题了

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 1007;
struct cow{
	int x, y, id;
}gon[N], goe[N];
struct point{
	int north, east, x, y;
	bool operator<(const point &a)const{
		if(x != a.x) return x < a.x;
		else return y < a.y;
	}
}p[N * N];
int n;
int totn, tote;
int tot;
bool del[N];
int ans[N];
int main(){
	cin >> n;
	for(int i = 1; i <= n; i++){
		char c;
		cin >> c;
		if(c == 'N'){
			totn++;
			cin >> gon[totn].x >> gon[totn].y;
			gon[totn].id = i;
		}
		else{
			tote++;
			cin >> goe[tote].x >> goe[tote].y;
			goe[tote].id = i;
		}
	}
	for(int i = 1; i <= totn; i++){
		for(int j = 1; j <= tote; j++){
			if(gon[i].x > goe[j].x && gon[i].y < goe[j].y){
				//printf("p:%d %d\n", gon[i].id, goe[j].id);
				p[++tot] = {i, j, gon[i].x, goe[j].y}; 
			}
		}
	}
	sort(p + 1, p + tot + 1);
	for(int i = 1; i <= tot; i++){
		int u = p[i].north, v = p[i].east;
		if(del[gon[u].id] || del[goe[v].id]) continue;
		if(p[i].y - gon[u].y < p[i].x - goe[v].x){
			ans[gon[u].id] += ans[goe[v].id] + 1;
			del[goe[v].id] = 1;
		}
		if(p[i].y - gon[u].y > p[i].x - goe[v].x){
			ans[goe[v].id] += ans[gon[u].id] + 1;
			del[gon[u].id] = 1;
		}
	}
	for(int i = 1; i <= n; i++){
		printf("%d\n", ans[i]);
	}
	return 0;
}

P8266 [USACO22OPEN] Photoshoot B

题目描述

迫切希望在郡县集市上赢得最佳奶牛摄影师的 Farmer John 正在尝试为他的 \(N\) 头奶牛拍摄一张完美的照片(\(2 \leq N \leq 2\cdot 10^5\)\(N\) 为偶数)。

Farmer John 拥有两种品种的奶牛:更赛牛(Guernsey)和荷斯坦牛(Holstein)。为了使他的照片尽可能地艺术,他想把他的奶牛排成一排,使得尽可能多的更赛牛处于队列中的偶数位置(队列中的第一个位置是奇数位置,下一个是偶数位置,以此类推)。由于他与他的奶牛缺乏有效的沟通,他可以达到目的的唯一方法是让他的奶牛的偶数长的「前缀」进行反转(一个前缀指的是对于某个位置 \(j\),从第一头奶牛到第 \(j\) 头奶牛范围内的所有奶牛)。

请计算 Farmer John 达到目的所需要的最小反转次数。

输入格式

输入的第一行包含 \(N\) 的值。

第二行包含一个长为 \(N\) 的字符串,给出初始时所有奶牛从左到右的排列方式。每个 'H' 代表一头荷斯坦牛,每个 'G' 代表一头更赛牛。

输出格式

输出一行,包含达到目的所需要的最小反转次数。

输入输出样例 #1

输入 #1

14
GGGHGHHGHHHGHG

输出 #1

1

说明/提示

【样例解释】

在这个例子中,只需反转由前六头奶牛组成的前缀即可。

   GGGHGHHGHHHGHG (反转前)
-> HGHGGGHGHHHGHG (反转后)

在反转之前,四头更赛牛处于偶数位置。反转后,六头更赛牛处于偶数位置。不可能使得超过六头更赛牛处于偶数位置。

【测试点性质】

  • 测试点 2-6 满足 \(N\le 1000\)
  • 测试点 7-11 没有额外限制。

题解:

因为题目限制了只能反转偶数长度的前缀
因此区间反转实际上就可以视作将这个区间内每个点下标的奇偶性反转
我们可以将相邻的2只奶牛化为一组来思考
设第\(i\)组奶牛的类型情况为\(a_i\)
1.2只奶牛的类型相同
那么无论如何反转显然都是无济于事的
就可以将其忽略
2.奇数位上奶牛为'G',偶数位上奶牛为'H'
显然反转这个区间会使答案\(+1\)
\(a_i=1\)
3.奇数位上奶牛为'H',偶数位上奶牛为'G'
显然反转这个区间会使答案\(-1\)
\(a_i=0\)
通过上面的分析
我们可以发现最终的答案一定是不含第2种情况的
为什么呢?
因为我们可以通过先反转区间\([1,l]\),再反转区间\([1,r]\)的方式来反转区间\([l,r]\)
那我们就可以先反转区间\([1,2x-2]\),再反转区间\([1,2x]\)使得答案增加
所以我们就要将\(a_i=1\)的区间全部消灭
\(m=n\div2\)
枚举区间\([2,m]\)
\(a_i\neq a_{i-1}\)
则要反转一次区间\([1,2i-2]\)使得区间\([1,i]\)\(a_i\)全部相等
\(Tips\):若\(a_m=1\),则要再反转一次

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 7;
int n;
string s;
int p[N >> 1], tot, ans;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n;
	cin >> s;
	s = ' ' + s;
	for(int i = 1; i <= n; i += 2){
		if(s[i] == s[i + 1]) continue;
		if(s[i] == 'G') p[++tot] = 1;
		else p[++tot] = 0;
	}
	if(!tot){
		printf("0\n");
		return 0;
	}
	for(int i = 2; i <= tot; i++){
		if(p[i] != p[i - 1]) ans++;
	}
	printf("%d\n", ans + p[tot]);
	return 0;
}

P7299 [USACO21JAN] Dance Mooves S

题目描述

Farmer John 的奶牛们正在炫耀她们的最新舞步!

最初,所有的 \(N\) 头奶牛(\(2≤N≤10^5\))站成一行,奶牛 \(i\) 排在其中第 \(i\) 位。舞步序列给定为 \(K\)\(1≤K≤2\times10^5\))个位置对 \((a_1,b_1),(a_2,b_2),…,(a_K,b_K)\)。在舞蹈的第 \(i=1…K\) 分钟,位置 \(a_i\)\(b_i\) 上的奶牛交换位置。同样的 \(K\) 次交换在第 \(K+1…2K\) 分钟发生,在第 \(2K+1…3K\) 分钟再次发生,以此类推,无限循环。换言之,

  • 在第 \(1\) 分钟,位置 \(a_1\)\(b_1\) 上的奶牛交换位置。
  • 在第 \(2\) 分钟,位置 \(a_2\)\(b_2\) 上的奶牛交换位置。
  • ……
  • 在第 \(K\) 分钟,位置 \(a_K\)\(b_K\) 上的奶牛交换位置。
  • 在第 \(K+1\) 分钟,位置 \(a_1\)\(b_1\) 上的奶牛交换位置。
  • 在第 \(K+2\) 分钟,位置 \(a_2\)\(b_2\) 上的奶牛交换位置。
  • 以此类推……

对于每头奶牛,求她在队伍中会占据的不同的位置数量。

输入格式

输入的第一行包含 \(N\)\(K\)。以下 \(K\) 行分别包含 \((a_1,b_1)…(a_K,b_K)\)\(1≤a_i<b_i≤N\))。

输出格式

输出 \(N\) 行,第 \(i\) 行包含奶牛 \(i\) 可以到达的不同的位置数量。

输入输出样例 #1

输入 #1

5 4
1 3
1 2
2 3
2 4

输出 #1

4
4
3
4
1

说明/提示

  • 奶牛 \(1\) 可以到达位置 \(\{1,2,3,4\}\)
  • 奶牛 \(2\) 可以到达位置 \(\{1,2,3,4\}\)
  • 奶牛 \(3\) 可以到达位置 \(\{1,2,3\}\)
  • 奶牛 \(4\) 可以到达位置 \(\{1,2,3,4\}\)
  • 奶牛 \(5\) 从未移动,所以她没有离开过位置 \(5\)

测试点性质:

  • 测试点 1-5 满足 \(N≤100,K≤200\)
  • 测试点 6-10 满足 \(N≤2000,K≤4000\)
  • 测试点 11-20 没有额外限制。

题解:

我们假设原本在位置\(x\)的一头奶牛\(A\)\(k\)分钟后走到了位置\(y\),原本在位置\(z\)的一头奶牛\(B\)\(k\)分钟后走到了位置\(x\)
那么奶牛\(B\)的运动轨迹也会和奶牛\(A\)的运动轨迹一样,只是会比奶牛\(A\)\(k\)分钟
那么,多组这样的奶牛就会构成一个环
那我们只需要把这若干个环处理出来就可以了
我们先用vector记录每头奶牛在\(k\)分钟内的途径点
再用数组记录每个位置在第\(k\)分钟时是哪头奶牛
用并查集去维护每个环
随后用set去维护每个环的大小
就可以愉快的AC了

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 7;
int n, m;
int a[N], f[N];
vector<int>g[N];
set<int>ans[N];
void init(){
	for(int i = 1; i <= n; i++){
		f[i] = a[i] = i;
		g[i].push_back(i);	
	}
}
int get(int x){
	if(x == f[x]) return x;
	else return f[x] = get(f[x]);
}
int main(){
	scanf("%d%d", &n, &m);
	init();
	for(int i = 1, x, y; i <= m; i++){
		scanf("%d%d", &x, &y);
		swap(a[x], a[y]);
		g[a[x]].push_back(x);
		g[a[y]].push_back(y);
	}
	for(int i = 1; i <= n; i++){
		int x = get(i), y = get(a[i]);
		f[x] = y;
	}
	for(int i = 1; i <= n; i++){
		for(int j : g[a[i]]){
			ans[get(a[i])].insert(j);
		}
	}
	for(int i = 1; i <= n; i++){
		printf("%d\n", ans[get(i)].size());
	}
	return 0;
} 

P7301 [USACO21JAN] Spaced Out S

题目描述

Farmer John 想要拍摄一张他的奶牛吃草的照片挂在墙上。草地可以用一个 \(N\)\(N\) 列正方形方格所组成的方阵表示(想象一个 \(N \times N\) 的棋盘),其中 \(2 \leq N \leq 1000\)。在 Farmer John 最近拍摄的照片中,他的奶牛们太过集中于草地上的某个区域。这一次,他想要确保他的奶牛们分散在整个草地上。于是他坚持如下的规则:

  • 没有两头奶牛可以位于同一个方格。
  • 所有 \(2 \times 2\) 的子矩阵(共有 \((N-1) \times (N-1)\) 个)必须包含恰好 2 头奶牛。

例如,这一放置方式是合法的:

CCC
...
CCC

而这一放置方式是不合法的,因为右下的 \(2 \times 2\) 正方形区域仅包含 1 头奶牛:

C.C
.C.
C..

没有其他限制。你可以假设 Farmer John 有无限多的奶牛(根据以往的经验,这种假设似乎是正确的……)。

Farmer John 更希望某些方格中包含奶牛。具体地说,他相信如果方格 \((i, j)\) 中放有一头奶牛,照片的美丽度会增加 \(a_{ij}\)\(0 \leq a_{ij} \leq 1000\))单位。

求合法的奶牛放置方式的最大总美丽度。

输入格式

输入的第一行包含 \(N\)。以下 \(N\) 行每行包含 \(N\) 个整数。从上到下第 \(i\) 行的第 \(j\) 个整数为 \(a_{ij}\) 的值。

输出格式

输出一个整数,为得到的照片的最大美丽度。

输入输出样例 #1

输入 #1

4
3 3 1 1
1 1 3 1
3 3 1 1
1 1 3 3

输出 #1

22

说明/提示

在这个样例中,最大美丽度可以在如下放置方式时达到:

CC..
..CC
CC..
..CC

这种放置方式的美丽度为 \(3 + 3 + 3 + 1 + 3 + 3 + 3 + 3 = 22\)

测试点性质:

  • 测试点 2-4 满足 \(N \le 4\)
  • 测试点 5-10 满足 \(N\le 10\)
  • 测试点 11-20 满足 \(N \le 1000\)

题解:

我们可以先枚举几个合法的方案
可以找到一个规律
任意一个合法的方案一定是行交替排列或列交替排列的
那我们就可以每行/列的奇数列/行和偶数列/行的和算出来
再取每行/列的奇偶最大值就可以了

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 1007;
int n;
int a[N][N];
int fx[N][2], fy[N][2];
int ans1, ans2;
int main(){
	scanf("%d", &n);
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			scanf("%d", &a[i][j]);
			fx[i][j % 2] += a[i][j];
			fy[j][i % 2] += a[i][j]; 
		}
	}
	for(int i = 1; i <= n; i++){
		ans1 += max(fx[i][0], fx[i][1]);
		ans2 += max(fy[i][0], fy[i][1]);
	}
	printf("%d\n", max(ans1, ans2));
	return 0;
}

P7410 [USACO21FEB] Just Green Enough S

题目描述

Farmer John 的草地可以被看作是一个由 \(N \times N\) 个正方形方格(\(1 \leq N \leq 500\))组成的方阵(想象一个巨大的棋盘)。由于土壤变异性,某些方格中的草可能更绿。每个方格 \((i,j)\) 可以用一个整数绿度值 \(G(i,j)\) 来描述,范围为 \(1 \ldots 200\)

Farmer John 想要给他的草地的一个子矩阵拍摄一张照片。他希望确保这一子矩阵看上去足够绿,但又不绿得过分,所以他决定拍摄一个 \(G\) 的最小值恰好等于 100 的子矩阵。请帮助他求出他可以拍摄多少不同的照片。子矩阵最大可以为整个草地,最小可以仅为一个方格(共有 \(N^2(N+1)^2/4\) 个不同的子矩阵——注意该数可能无法用 \(32\) 位整数型存储,所以你可能需要使用 \(64\) 位整数类型,例如 C++ 中的 long long)。

输入格式

输入的第一行包含 \(N\)。以下 \(N\) 行每行包含 \(N\) 个整数,表示 \(N \times N\) 草地的 \(G(i,j)\) 值。

输出格式

输出 Farmer John 可以拍摄的不同的照片数量——也就是说,最小绿度值等于 \(100\) 的子矩阵数量。

注意这个问题涉及到的整数大小可能需要使用 \(64\) 位整数型存储(例如,C/C++ 中的 long long)。

输入输出样例 #1

输入 #1

3
57 120 87
200 100 150
2 141 135

输出 #1

8

说明/提示

测试点性质:

  • 对于 \(50\%\) 的数据,满足 \(N\le 200\)
  • 对于另外 \(50\%\) 的数据,没有额外限制。

题解:

这题\(N \le 500\)提示着我们要使用至少\(O(n^3)\)的算法
但是答案要求最小值恰好为\(100\)有点难实现
我们可利用差分的思想将其转化一下
\(f(x)\)为最小值\(\geq x\)的子矩阵个数
那么答案就等于\(f(101)-f(100)\)
现在开始考虑\(f\)函数的实现
我们可以给\(a\)数组做一个标记
设这个标记数组为\(b\)
\(a_{i,j} \geq x\)时,\(b_{i,j}=1\)
反之,当\(a_{i,j} < x\)时,\(b_{i,j}=0\)
那么一个矩阵符合要求当且仅当矩阵内每个数的\(b\)数组的值都为1
随后我们可以枚举每个点作为右下角
那我们就可以求出这个点上方的每行最多可以往左扩展几格
并做一个后缀最小值
为什么要做后缀最小值呢?
我们设当前矩阵右下角在第\(i\)行,考虑到了第\(j\)
那么如果这个矩阵的宽为\(k\)
那么从\(j\)\(i\)之间的每一行都需要往左扩展\(k-1\)
我们可以利用\(b\)数组去预处理出每个点最多可以往左扩展几格
我们将其命名为\(sum\)
\(b_{i,j}=1\),则\(sum_{i,j}=sum_{i,j-1}+1\)
反之,当\(b_{i,j}=0\),则\(sum_{i,j}=0\)
那么我们就可以愉快的AC了

Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 507;
int n;
int a[N][N], sum[N][N];
bool b[N][N];
int up[N][N], down[N][N], lft[N][N], rit[N][N];
ll query(int t){
	ll ans = 0;
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			if(a[i][j] < t) b[i][j] = 0, sum[i][j] = 0;
			else b[i][j] = 1, sum[i][j] = sum[i][j - 1] + 1;
		}
	}
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			if(!b[i][j]) continue;
			int m = 1e9;
			for(int k = i; k; k--){
				m = min(m, sum[k][j]);
				ans += m;
			}
		}
	}
	return ans;
}
int main(){
	/*freopen("ls.in", "r", stdin);
	freopen("ls.out", "w", stdout);*/
	scanf("%d", &n);
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			scanf("%d", &a[i][j]);
		}
	}
	printf("%lld", -query(101) + query(100));
	return 0;
}
posted @ 2025-10-28 10:06  EzSun599  阅读(10)  评论(0)    收藏  举报