99. 激光炸弹(二维前缀和)

题目链接
一种新型的激光炸弹,可以摧毁一个边长为 R 的正方形内的所有的目标。

现在地图上有 N 个目标,用整数Xi,Yi表示目标在地图上的位置,每个目标都有一个价值Wi。

激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个边长为 R 的正方形的边必须和x,y轴平行。

若目标位于爆破正方形的边上,该目标不会被摧毁。

求一颗炸弹最多能炸掉地图上总价值为多少的目标。

输入格式
第一行输入正整数 N 和 R ,分别代表地图上的目标数目和正方形的边长,数据用空格隔开。

接下来N行,每行输入一组数据,每组数据包括三个整数Xi,Yi,Wi,分别代表目标的x坐标,y坐标和价值,数据用空格隔开。

输出格式
输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。

数据范围
0<N≤10000,
0≤Xi,Yi≤5000
输入样例:
2 1
0 0 1
1 1 1

输出样例:
1

题解:
A点的前缀和就等于 g[i][j] += g[i - 1][j] + g[i][j - 1] - g[i - 1][j - 1];
在这里插入图片描述

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 5050;
int n,r,row,col,x,y,w,ans,g[N][N]; 
int main(){
	cin >> n >> r;
	row = col = r;
	for(int i = 0;i < n;i++){
		cin >> x >> y >> w;
		x++,y++;//原来下标为0-(n - 1),这样就把下标改成1 - n
		g[x][y] = w;
		row = max(row,x),col = max(col,y);
	}
	for(int i = 1;i <= row;i++){
		for(int j = 1;j <= col;j++){//下标为 1-n这样就不用考虑越界问题了
			g[i][j] += g[i - 1][j] + g[i][j - 1] - g[i - 1][j - 1]; 
		}
	}
	ans = -1;
	for(int i = r;i <= row;i++){
		for(int j = r;j <= col;j++){
			ans = max(ans,g[i][j] - g[i - r][j] - g[i][j - r] + g[i - r][j - r]);
		}
	}
	cout << ans << endl;
	return 0;
}
<think>我们正在使用Cocos Creator 2.x版本(JavaScript)来实现掼蛋游戏的二维手牌展示和选牌逻辑。 掼蛋游戏通常每位玩家手牌数量较多(如13张),因此需要合理地布局展示,并且支持玩家选择多张牌(如选对子、顺子等)。 实现思路: 1. 手牌布局:将手牌以扇形或圆弧形排列,或者水平排列并重叠展示(常见于斗地主类游戏),但掼蛋通常采用水平展开,选中时提升并放大。 2. 选牌逻辑:点击一张牌,将其设为选中状态,并提升到最前面(避免被其他牌遮挡),同时记录选中的牌。可以支持多张连续选择或者多张任意选择(根据规则,一般选牌是任意选,但需要符合牌型规则,这里我们只处理选择逻辑,牌型判断由其他逻辑处理)。 步骤: 1. 创建手牌节点:每张牌是一个Prefab,包含牌面信息(花色和点数)和点击事件。 2. 布局:根据手牌数量,计算每张牌的位置和旋转角度(如果是扇形布局),或者计算每张牌的偏移位置(水平布局,每张牌有一定重叠)。 3. 选中效果:当牌被选中时,将其位置提高(y轴向上移动),并放大一点,同时调整层级(zIndex)到最前面。 4. 多选处理:可以设置点击选中的牌加入一个选中数组,再次点击则取消选中(恢复原位置和大小,并降低层级)。 5. 选牌提交:提供一个出牌按钮,点击后提交当前选中的牌组。 注意:掼蛋游戏规则中,选牌后需要符合一定的牌型(如单张、对子、三张、顺子、同花顺、炸弹等),但这里我们只关注选牌逻辑和展示。 具体实现: 1. 创建手牌预制体(CardPrefab): - 包含一个Sprite组件用于显示牌面(牌背和牌面,根据情况切换) - 在属性检查器中,我们可以添加一个自定义脚本组件(如Card.js)来保存这张牌的花色(suit)和点数(rank)信息,并处理点击事件。 2. 在游戏场景中,创建一个节点(如HandCardArea)作为手牌容器。 3. 初始化手牌: - 从服务器或本地获得手牌数据(一个数组,包含每张牌的花色和点数)。 - 实例化CardPrefab,设置每张牌的显示(根据花色和点数设置对应的图片),并将它们添加到HandCardArea中。 4. 布局计算(水平排列): - 假设每张牌的宽度为100(根据实际图片大小调整),我们希望手牌在容器中水平居中,并且每张牌之间有部分重叠(比如重叠1/3,即偏移70)。 - 总宽度 = (牌数-1) * 偏移量 + 单张牌宽度 - 起始X坐标 = -总宽度/2 + 单张牌宽度/2 - 然后依次设置每张牌的位置:x = 起始X + 索引 * 偏移量,y=0(或根据情况调整) 5. 点击事件处理(在Card.js中): - 在Card.js的onLoad函数中,为节点添加点击事件:`this.node.on('click', this.onClick, this);` - 定义onClick函数:切换选中状态,并改变位置和大小,同时通知手牌管理脚本(如HandCardManager)更新选中状态。 6. 手牌管理脚本(HandCardManager): - 负责创建手牌实例,布局,以及处理选中的牌。 - 维护一个选中的牌数组(selectedCards),当一张牌被点击时,如果它已经被选中,则取消选中;如果未被选中,则将其加入选中数组(并检查是否超过最大可选张数?根据规则,一般没有限制,但出牌时需要符合牌型,所以这里不做限制)。 7. 选中效果: - 选中:将牌向上移动一定距离(如20像素),缩放设为1.1(原大小1.0),并将层级(zIndex)设置为当前最高(可以设置一个全局的zIndex计数器,每次选中时增加,并赋值给该节点)。 - 取消选中:恢复原位置,缩放恢复1.0,层级恢复默认(可以设置为0,然后重新排列层级?但是注意,取消选中后,其他牌可能已经改变了层级,我们可以将未选中的牌按顺序设置层级,选中的牌层级始终高于未选中的牌)。 8. 重新布局问题:当选中一张牌时,它周围的牌可能需要重新排列(避免被遮挡)?实际上,由于我们提高了选中牌的位置和层级,所以不需要重新排列。但当我们取消选中时,需要将牌放回原位置。 9. 另一种实现:选中牌时,将其从原位置取出,放在一个单独的选中区域(比如手牌区域上方),这样就不会影响其他牌的布局。取消选中时再放回原位置。这样可能更简单。 10. 我们采用的方法:在布局时,每张牌都有一个基础位置(包括x, y, 缩放,旋转)。当牌被选中时,我们将其基础位置向上偏移并放大,同时设置高层级。当取消选中时,恢复基础位置和缩放,并设置一个较低的层级(但注意,未选中的牌之间也有层级关系,后面的牌层级应该比前面的牌高,这样才会显示重叠效果?实际上,我们通常让后面的牌覆盖前面的牌,所以后面的牌层级应该更高。但为了选中时不被遮挡,我们提高选中牌的层级)。 11. 层级管理:在布局时,我们可以设置每张牌的zIndex为它的索引值(这样后面的牌会覆盖前面的牌)。当一张牌被选中时,我们将它的zIndex设置为一个很大的值(比如1000+索引),这样它就会在最上面。当取消选中时,将其zIndex重置为原来的索引。 12. 但是注意:如果多张牌被选中,它们之间也需要有层级,应该按照选中的先后顺序设置层级(后选中的在最上面)?或者按照原始顺序?这里我们可以按照选中的顺序,后选中的放在上面。所以我们需要记录选中的顺序。 13. 因此,在HandCardManager中,我们维护一个选中的数组(按选中顺序),当一张牌被选中时,将其加入数组末尾,并设置它的层级为当前最大层级+1(这样后选中的牌总是在上面)。当取消选中时,从数组中移除,并重置层级为原始层级(即它在手牌中的位置索引)。 14. 但是,当牌被选中后,其他未选中的牌的位置和层级是否需要调整?实际上,未选中的牌我们不需要调整层级,因为它们的层级就是它们在数组中的索引。但是,当一张牌被取消选中时,我们需要将它的层级设置回原来的索引(注意,此时手牌数组中,这张牌的位置并没有变,所以索引也没变)。 15. 具体步骤: 布局时: for (let i = 0; i < cards.length; i++) { let card = cards[i]; card.node.setPosition(起始X + i * 偏移量, 0); card.node.zIndex = i; // 这样后面的牌会覆盖前面的牌 // 记录基础位置 card.originalPos = cc.v2(起始X + i * 偏移量, 0); card.originalScale = 1.0; card.originalZIndex = i; } 选中时(在Card.js中,调用HandCardManager的方法): HandCardManager.selectCard(this); // 将当前牌对象传给管理器 在HandCardManager的selectCard方法中: if (card.selected) { // 如果已经选中,则取消选中 card.selected = false; // 从selectedCards数组中移除 let index = this.selectedCards.indexOf(card); if (index !== -1) { this.selectedCards.splice(index, 1); } // 恢复基础状态 card.node.position = card.originalPos; card.node.scale = card.originalScale; card.node.zIndex = card.originalZIndex; } else { card.selected = true; this.selectedCards.push(card); // 设置选中状态:位置提高,放大,层级提高 let newPos = cc.v2(card.originalPos.x, card.originalPos.y + 20); card.node.position = newPos; card.node.scale = 1.1; // 设置层级:当前最大层级+1(为了避免和未选中的牌冲突,我们设置一个基础值,比如1000,然后加上当前选中的数量) card.node.zIndex = 1000 + this.selectedCards.length; } 注意:当取消选中时,我们直接恢复了原始层级,这样它就会回到原来的层级位置(被后面未选中的牌覆盖,但覆盖关系是合理的)。但是,如果有多张牌被选中,然后取消其中一张,那么其他选中的牌层级不会改变,所以没有问题。 16. 问题:当一张牌被选中后,它被提高了层级,但是当它取消选中时,我们将其层级设置回原始层级(比如0),而其他未选中的牌层级可能是0,1,2,...,那么这张牌可能会被后面的牌覆盖(因为它的层级0小于后面的牌),这符合我们的要求(后面的牌覆盖前面的牌)。但是,在选中时,我们希望选中的牌都在最上面,所以使用一个很大的层级(1000以上)来确保它们总是在未选中的牌之上。 17. 重新布局:如果手牌数量发生变化(比如出牌后),我们需要重新布局。重新布局时,需要重新计算每张牌的位置和层级(包括更新原始位置和层级),并且将选中的牌取消选中(或者根据情况重新设置选中状态?)但注意,出牌后,被选中的牌会被移除,所以不需要考虑。 18. 出牌:当用户点击出牌按钮时,我们获取selectedCards数组,将选中的牌从手牌中移除,并重新布局手牌。 19. 代码结构: - HandCardManager.js(挂载在HandCardArea节点上) 属性: cardPrefab: cc.Prefab // 手牌预制体 cardSpacing: 70 // 牌之间的间隔(水平方向,实际重叠部分为:牌宽-间隔) cardWidth: 100 // 单张牌宽度 selectedCards: [] // 选中的牌数组 方法: initCards(cardsData) // 初始化手牌,cardsData是数组,如[{suit:1,rank:1}, ...] layoutCards() // 对手牌进行布局 selectCard(card) // 选中/取消选中牌 playCards() // 出牌(将选中的牌打出) - Card.js(挂载在每张牌预制体上) 属性: suit: 0 // 花色 rank: 0 // 点数 selected: false // 是否选中 originalPos: cc.Vec2 // 原始位置 originalScale: 1.0 // 原始缩放 originalZIndex: 0 // 原始层级 方法: init(data) // 初始化,设置suit和rank,并更新显示 onClick() // 点击事件,调用HandCardManager的selectCard方法 20. 注意:在Card.js中,我们需要引用HandCardManager,可以通过全局变量或者查找父节点的方式。建议在Card的onLoad函数中,查找父节点上的HandCardManager组件。 21. 示例代码(HandCardManager.js的initCards方法): initCards: function (cardsData) { // 先清空手牌区域 this.node.removeAllChildren(); // 排序(如果需要,按掼蛋规则排序) // 这里假设cardsData已经排序 this.cards = []; // 保存所有手牌对象(Card组件) for (let i = 0; i < cardsData.length; i++) { let cardNode = cc.instantiate(this.cardPrefab); this.node.addChild(cardNode); let card = cardNode.getComponent('Card'); card.init(cardsData[i]); // 初始化单张牌 card.manager = this; // 将管理器保存到card中,方便调用 this.cards.push(card); } this.layoutCards(); } layoutCards: function () { let count = this.cards.length; let totalWidth = (count - 1) * this.cardSpacing + this.cardWidth; let startX = -totalWidth / 2 + this.cardWidth / 2; for (let i = 0; i < count; i++) { let card = this.cards[i]; card.originalPos = cc.v2(startX + i * this.cardSpacing, 0); card.originalScale = 1.0; card.originalZIndex = i; // 如果这张牌没有被选中,则恢复原始位置和缩放 if (!card.selected) { card.node.setPosition(card.originalPos); card.node.setScale(card.originalScale); card.node.zIndex = card.originalZIndex; } else { // 如果已经被选中,则使用选中的位置(但布局时,我们可能希望选中的牌仍然在基础位置上提高,所以这里我们仍然设置基础位置,但选中状态会改变位置) // 注意:选中的牌的位置是基于原始位置提高的,所以这里我们不需要改变,但层级需要重新设置(因为重新布局后,原始层级可能变了?) // 但是注意,重新布局后,我们改变了原始位置,所以选中牌的位置应该重新计算(提高的位置也是基于新的原始位置) // 因此,我们在这里重新设置选中状态的位置(提高) card.node.setPosition(card.originalPos.x, card.originalPos.y + 20); // 缩放已经在选中时设置过了,这里不变 // 层级:我们仍然需要保持它在选中状态中的层级(在selectCard方法中设置的层级),所以这里不改变层级 } } } 注意:重新布局时,如果牌已经被选中,我们需要重新计算它的位置(因为原始位置变了),但是选中的牌的位置是原始位置+偏移量。所以我们在layoutCards中,对每张牌都设置了原始位置,然后如果是选中的牌,我们再额外设置一个偏移。但是,这样会覆盖掉选中的牌的位置(因为每次布局都会重新设置位置)。所以我们需要在布局后,对选中的牌重新设置一次偏移位置。但是,我们也可以在layoutCards中,对选中的牌直接设置偏移位置(如上面代码所示)。 22. 在HandCardManager的selectCard方法中,我们不再需要设置位置?不对,在selectCard方法中,我们仍然需要设置位置(因为点击时触发)。但是,在重新布局时(layoutCards),我们也会设置位置(包括选中的牌)。所以我们需要在layoutCards中处理选中的牌的位置偏移。 23. 因此,在selectCard方法中,我们只改变选中的状态,并更新层级,然后调用layoutCards()来重新布局?这样也可以,但是重新布局会遍历所有牌,性能不是最佳。但是手牌最多13张,所以可以接受。 24. 修改selectCard方法: selectCard: function (card) { if (card.selected) { // 取消选中 card.selected = false; let index = this.selectedCards.indexOf(card); if (index !== -1) { this.selectedCards.splice(index, 1); } } else { card.selected = true; this.selectedCards.push(card); } // 重新布局(会重新设置所有牌的位置和层级,包括选中的牌) this.layoutCards(); } 然后在layoutCards中,我们根据每张牌的selected状态来设置不同的位置和层级: for (let i = 0; i < count; i++) { let card = this.cards[i]; card.originalPos = cc.v2(startX + i * this.cardSpacing, 0); card.originalScale = 1.0; card.originalZIndex = i; if (!card.selected) { card.node.setPosition(card.originalPos); card.node.setScale(card.originalScale); card.node.zIndex = card.originalZIndex; } else { // 选中状态:位置提高,缩放变大,层级提高(在选中数组中的顺序) // 在选中数组中的索引(从0开始,但层级要比未选中的高) let indexInSelected = this.selectedCards.indexOf(card); if (indexInSelected === -1) { // 不应该发生,说明状态不一致 card.selected = false; card.node.setPosition(card.originalPos); card.node.setScale(card.originalScale); card.node.zIndex = card.originalZIndex; } else { card.node.setPosition(card.originalPos.x, card.originalPos.y + 20); card.node.setScale(1.1); // 层级:1000 + 在选中数组中的索引(这样后选中的牌层级更高,因为后加入的索引更大) card.node.zIndex = 1000 + indexInSelected; } } } 这样,在选中数组中的顺序就是选中的先后顺序,而且后选中的牌层级更高(显示在上面)。 25. 问题:在选中数组中的索引是加入的顺序,所以先选中的索引小,后选中的索引大,这样后选中的牌层级更高(1000+更大的索引),所以会显示在先选中的牌的上面。这符合需求。 26. 性能:每次点击一张牌,就会重新布局所有牌(13张),但13次操作在Cocos Creator中是可以接受的。 27. 出牌后,从手牌中移除选中的牌,然后重新布局: playCards: function () { // 将选中的牌从手牌数组中移除 for (let i = 0; i < this.selectedCards.length; i++) { let card = this.selectedCards[i]; // 从this.cards中移除 let index = this.cards.indexOf(card); if (index !== -1) { this.cards.splice(index, 1); } // 销毁节点 card.node.destroy(); } // 清空选中数组 this.selectedCards = []; // 重新布局 this.layoutCards(); } 28. 注意:在出牌后,手牌数组(this.cards)发生了变化,所以重新布局时,牌的数量和顺序都变了。 29. 排序:掼蛋游戏需要按照特定规则排序(比如按点数,再按花色),所以在初始化手牌时,应该先对牌进行排序。可以在initCards之前排序,也可以在layoutCards中按照位置排序(但最好在数据层面排序)。 30. 在initCards中,我们可以对cardsData排序,然后再创建节点。 31. 关于选牌逻辑,我们实现了多选和取消选择,以及选中的牌会抬高并放大,层级提高。 32. 最后,我们还需要处理牌面的显示,即根据suit和rank设置对应的图片。在Card.js的init方法中: init: function (data) { this.suit = data.suit; this.rank = data.rank; // 根据suit和rank设置图片 // 假设我们有一个图片资源数组,或者使用图集 // 这里假设有一个方法getCardSpriteFrame(suit, rank)来获取对应的SpriteFrame let sprite = this.node.getComponent(cc.Sprite); sprite.spriteFrame = this.getCardSpriteFrame(this.suit, this.rank); } 33. 获取图片资源的方法可以根据项目实际情况,比如从资源管理器中获取。 34. 另外,为了美观,可以添加一些动画效果(比如使用cc.tween)来平滑移动。 总结:通过以上步骤,我们可以在Cocos Creator 2.x中实现掼蛋游戏的二维手牌展示和选牌逻辑。 注意:以上代码是思路描述,实际编写时可能需要调整。 相关问题: 1. 如何实现手牌的拖拽操作(比如拖动选中的牌)? 2. 如何优化手牌布局的性能(当手牌数量较多时)? 3. 如何实现手牌的排序功能(按点数、花色等)? 4. 如何实现牌的组合提示(如自动提示顺子、对子等)?</think>在 Cocos Creator 2.x 中实现掼蛋游戏的二维手牌布局与选牌逻辑,需结合节点操作、触摸事件和布局算法。以下是分步实现方案: --- ### **1. 手牌预制体设计** 创建牌面预制体 `CardPrefab`: - **节点结构**: ```bash CardPrefab (cc.Sprite) ├─ Front (牌面图片) └─ Back (牌背图片) ``` - **组件脚本** `Card.js`: ```javascript cc.Class({ properties: { cardId: 0, // 唯一标识 isSelected: false, // 选中状态 frontSprite: cc.Sprite, // 牌面组件 backSprite: cc.Sprite // 牌背组件 }, init(data) { this.cardId = data.id; // 加载牌面纹理 cc.loader.loadRes(...) this.toggleFaceUp(true); // 默认显示正面 }, toggleFaceUp(isUp) { this.frontSprite.node.active = isUp; this.backSprite.node.active = !isUp; }, setSelected(selected) { this.isSelected = selected; this.node.y = selected ? 20 : 0; // 选中时上浮 } }); ``` --- ### **2. 二维手牌布局算法** **水平弧形布局**(在 `HandCardManager.js` 中实现): ```javascript updateLayout() { const cardCount = this.cards.length; const totalWidth = this.cardWidth * Math.min(cardCount, 10); // 最大展开宽度 const radius = totalWidth / (2 * Math.sin(Math.PI * this.arcAngle / 360)); this.cards.forEach((card, idx) => { const angle = (idx - (cardCount-1)/2) * this.arcAngle / (cardCount-1); const radian = angle * Math.PI / 180; const x = radius * Math.sin(radian); const y = radius * (1 - Math.cos(radian)) - this.yOffset; card.node.setPosition(x, y); card.node.rotation = -angle; // 扇形旋转 card.node.zIndex = idx; // 层级管理 }); } ``` - **参数说明**: - `arcAngle`:总展开角度(建议 60°-90°) - `yOffset`:垂直偏移量(避免重叠 UI) - `cardWidth`:单张牌宽度 --- ### **3. 选牌逻辑实现** **多选与取消逻辑**(在 `Card.js` 中): ```javascript onLoad() { this.node.on(cc.Node.EventType.TOUCH_END, () => { const newState = !this.isSelected; this.setSelected(newState); // 通知手牌管理器 const manager = this.node.parent.getComponent('HandCardManager'); manager.onCardSelect(this.cardId, newState); }); } ``` **管理器逻辑**(在 `HandCardManager.js` 中): ```javascript onCardSelect(cardId, isSelected) { if (isSelected) { this.selectedIds.add(cardId); // 加入选中集合 } else { this.selectedIds.delete(cardId); // 移除选中 } // 限制最大选中数(如炸弹4张) if (this.selectedIds.size > 4) { // 自动取消最早选中的牌 const firstId = Array.from(this.selectedIds)[0]; const firstCard = this.cards.find(c => c.cardId === firstId); firstCard.setSelected(false); this.selectedIds.delete(firstId); } } ``` --- ### **4. 动态效果优化** - **缓动动画**: ```javascript // 在 updateLayout 中替换 setPosition cc.tween(card.node) .to(0.2, { position: cc.v2(x, y), rotation: -angle }) .start(); ``` - **层级管理**: 选中时临时提高 `zIndex`: ```javascript card.node.zIndex = (this.isSelected ? 100 : 0) + idx; ``` --- ### **5. 出牌逻辑整合** ```javascript // HandCardManager.js submitCards() { const selectedCards = this.cards.filter(c => this.selectedIds.has(c.cardId)); // 验证牌型(顺子/对子/炸弹等) if (this.checkCardType(selectedCards)) { selectedCards.forEach(card => card.node.destroy()); this.cards = this.cards.filter(c => !c.isSelected); this.updateLayout(); } } ``` --- ### **常见问题解决** 1. **牌面重叠**: - 调整 `arcAngle` 角度值 - 增加 `cardSpacing` 水平间距参数 2. **触摸穿透**: - 添加 `cc.BlockInputEvents` 组件到手牌容器 3. **性能优化**: - 对象池管理 `cc.NodePool` - 避免每帧更新布局(仅在牌数变化时调用) --- ### **相关问题** 1. 如何实现手牌的拖拽排序功能? 2. 怎样优化大量手牌(如 27 张)的渲染性能? 3. 如何实现牌型自动识别(顺子/炸弹)算法? 4. 在 Cocos Creator 中如何实现卡牌的 3D 翻转效果? > 引用提示:掼蛋游戏规则中牌型组合逻辑需参考《中国掼蛋竞赛规则》[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值