IM即时通讯群组头像拼接.net core 解决方案
一、需求概述
多人聊天(群组,讨论组,聊天室,以下统称: “群组” )生成一个拼接头像,需要把最先加入群组的几个人(最多4个人,以下简称:头部用户,A、B、C、D)的头像拼凑成在一起。
群组创建后,A、B、C、D其中任何一个修改了自己的头像,需要 "异步" 更新群组头像。
以上是简单的需求描述。
本文使用.net core实现了N张图片拼接算法。
完整代码点击:https://github.com/night-king/ImageMerge
二、方案实现探讨
本需求可以在服务端实现,也可以在客户端实现。
(1)服务端实现:
群组创建时,下载A、B、C、D的头像,然后合并即可;
头部用户修改头像后,后台任务刷新群组头像。
(2)客户端实现:客户端包括iOS,Android,React,Web。
情况有些复杂,所以最后决定直接采用服务端实现。
Github上搜索无果,只能自己动手完成了。
先看效果:
采用四位Github大神头像作为源图片:

结果如下:

三、解决方案
群组2个用户,3个用户,4个以及以上用户头像不一样,总结如下:
/// <summary>
/// 合并布局枚举
/// </summary>
public enum Merge2LayoutEnum
{
/// <summary>
/// 2张图片,上下各1个长方形
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// | |
/// | R2 |
/// | |
/// ———————————————————
/// </summary>
Merge2R1 = , /// <summary>
/// 2张图片,左右各1个长方形
/// ———————————————————
/// | | |
/// | | |
/// | | |
/// | R1 | R2 |
/// | | |
/// | | |
/// | | |
/// ———————————————————
/// </summary>
Merge2R2 =
} public enum Merge3LayoutEnum
{ /// <summary>
/// 3张图片, 上面一个长方形,下面2个正方形并排
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// </summary>
Merge1R2S1 = , /// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// </summary>
Merge1R2S2 = , /// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | | S1 |
/// | | |
/// | R1 |—————————
/// | | |
/// | | S2 |
/// | | |
/// ———————————————————
/// </summary>
Merge1R2S3 = , /// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | S2 | |
/// | | |
/// |—————————| R1 |
/// | | |
/// | S2 | |
/// | | |
/// ———————————————————
/// </summary>
Merge1R2S4 =
} public enum Merge4LayoutEnum
{
/// <summary>
/// 4张图片,上面一个长方形,下面2个正方形并排
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// | | |
/// | S3 | S4 |
/// | | |
/// ———————————————————
/// </summary>
Merge4S = }
以下是2张图片实现代码:
/// <summary>
/// 合并2张图片
/// </summary>
/// <param name="image1"></param>
/// <param name="image2"></param>
/// <param name="layout"></param>
/// <returns></returns>
public static Image Merge2Images(Image image1, Image image2, Merge2LayoutEnum layout = Merge2LayoutEnum.Merge2R1, int size = )
{
var width = size;
var height = size;
var pf = PixelFormat.Format32bppArgb;
using (var bg = new Bitmap(width, height, pf))
{
using (var g = Graphics.FromImage(bg))
{
g.FillRectangle((Brush)Brushes.White, , , width, height);//全幅背景为白色
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
switch (layout)
{
/// <summary>
/// 2张图片,上下各1个长方形
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// | |
/// | R2 |
/// | |
/// ———————————————————
/// </summary>
case Merge2LayoutEnum.Merge2R1:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width;// =250
var srcWidth = img1.Width;// =250
var srcHeight = img1.Height * newHeight / newWidth;//=250*125/250=125
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ;// =125
var newWidth = width; // =250
var srcWidth = img2.Width;// =250
var srcHeight = img2.Height * newHeight / newWidth;//=250*125/250=125
g.DrawImage(img2, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
/// <summary>
/// 2张图片,左右各1个长方形
/// ———————————————————
/// | | |
/// | | |
/// | | |
/// | R1 | R2 |
/// | | |
/// | | |
/// | | |
/// ———————————————————
/// </summary>
case Merge2LayoutEnum.Merge2R2:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newWidth = width / ;// =125
var newHeight = height; // =250
var srcWidth = img1.Width * newWidth / newHeight;//=250*125/250=125
var srcHeight = img1.Height;// =250
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newWidth = width / ;// =125
var newHeight = height; // =250
var srcWidth = img2.Width * newWidth / newHeight;//=500*125/250=250
var srcHeight = img2.Height;// =500
g.DrawImage(img2, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
}
g.Save();
image1.Dispose();
image2.Dispose();
} using (var ms = new MemoryStream())
{
bg.Save(ms, ImageFormat.Png);
var buffers = ms.ToArray();
return ConvertToImage(buffers);
}
}
}
以下是三种图片的生成算法
/// <summary>
/// 合并3张图片
/// </summary>
/// <param name="image1"></param>
/// <param name="image2"></param>
/// <param name="image3"></param>
/// <param name="layout"></param>
/// <returns></returns>
public static Image Merge3Images(Image image1, Image image2, Image image3, Merge3LayoutEnum layout = Merge3LayoutEnum.Merge1R2S1, int size = )
{
var width = size;
var height = size;
var pf = PixelFormat.Format32bppArgb;
using (var bg = new Bitmap(width, height, pf))
{
using (var g = Graphics.FromImage(bg))
{
g.FillRectangle((Brush)Brushes.White, , , width, height);//全幅背景为白色
switch (layout)
{
/// <summary>
/// 3张图片, 上面一个长方形,下面2个正方形并排
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// </summary>
case Merge3LayoutEnum.Merge1R2S1:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width;// =250
var srcWidth = img1.Width;// =250
var srcHeight = img1.Height * newHeight / newWidth;//=250*125/250=125
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// =250
g.DrawImage(img2, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img3.Width; // =250
var srcHeight = img3.Height;// =250
g.DrawImage(img3, new Rectangle(width / , height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
/// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// | |
/// | R1 |
/// | |
/// ———————————————————
/// </summary>
case Merge3LayoutEnum.Merge1R2S2:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img1.Width; // =250
var srcHeight = img1.Height;// =250
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// =250
g.DrawImage(img2, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height / ;// =125
var newWidth = width;// =250
var srcWidth = img3.Width;// =250
var srcHeight = img3.Height * newHeight / newWidth;//=250*125/250=125
g.DrawImage(img3, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
/// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | | S1 |
/// | | |
/// | R1 |—————————
/// | | |
/// | | S2 |
/// | | |
/// ———————————————————
/// </summary>
case Merge3LayoutEnum.Merge1R2S3:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height;// =250
var newWidth = width / ;// =125
var srcHeight = img1.Height;//=250
var srcWidth = img1.Width * newWidth / newHeight;// =250*125/250=125;
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// = 250
g.DrawImage(img2, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img3.Width; // =250
var srcHeight = img3.Height;// =250
g.DrawImage(img3, new Rectangle(width / , height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
/// <summary>
/// 3张图片,上面2个正方形并排,下面一个长方形
/// ———————————————————
/// | | |
/// | S2 | |
/// | | |
/// |—————————| R1 |
/// | | |
/// | S2 | |
/// | | |
/// ———————————————————
/// </summary>
case Merge3LayoutEnum.Merge1R2S4:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img1.Width; // =250
var srcHeight = img1.Height;// =250
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// =250
g.DrawImage(img2, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height;// =250
var newWidth = width / ;// =125
var srcHeight = img3.Height;//=250
var srcWidth = img3.Width * newWidth / newHeight;// =250*125/250=125;
g.DrawImage(img3, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
}
g.Save();
image1.Dispose();
image2.Dispose();
image3.Dispose();
}
using (var ms = new MemoryStream())
{
bg.Save(ms, ImageFormat.Png);
var buffers = ms.ToArray();
return ConvertToImage(buffers);
}
}
}
以下是4张图片的生成是算法:
/// <summary>
/// 合并4张图片
/// </summary>
/// <param name="image1"></param>
/// <param name="image2"></param>
/// <param name="image3"></param>
/// <param name="image4"></param>
/// <param name="layout"></param>
/// <returns></returns>
public static Image Merge4Images(Image image1, Image image2, Image image3, Image image4, Merge4LayoutEnum layout = Merge4LayoutEnum.Merge4S, int size = )
{
var width = size;
var height = size;
var pf = PixelFormat.Format32bppArgb;
using (var bg = new Bitmap(width, height, pf))
{
using (var g = Graphics.FromImage(bg))
{
g.FillRectangle((Brush)Brushes.White, , , width, height);//全幅背景为白色
switch (layout)
{
/// <summary>
/// 4张图片,上面一个长方形,下面2个正方形并排
/// ———————————————————
/// | | |
/// | S1 | S2 |
/// | | |
/// ———————————————————
/// | | |
/// | S3 | S4 |
/// | | |
/// ———————————————————
/// </summary>
case Merge4LayoutEnum.Merge4S:
{
using (var img1 = ZoomToSqure(image1, size))
{
var newHeight = height / ;// =125
var newWidth = width / ; // =125
var srcWidth = img1.Width; // =250
var srcHeight = img1.Height;// =250
g.DrawImage(img1, new Rectangle(, , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img2 = ZoomToSqure(image2, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img2.Width; // =250
var srcHeight = img2.Height;// =250
g.DrawImage(img2, new Rectangle(width / , , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img3 = ZoomToSqure(image3, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img3.Width; // =250
var srcHeight = img3.Height;// =250
g.DrawImage(img3, new Rectangle(, height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
using (var img4 = ZoomToSqure(image4, size))
{
var newHeight = height / ; // =125
var newWidth = width / ; // =125
var srcWidth = img4.Width; // =250
var srcHeight = img4.Height;// =250
g.DrawImage(img4, new Rectangle(width / , height / , newWidth, newHeight), new Rectangle(, , srcWidth, srcHeight), GraphicsUnit.Pixel);
}
}
break;
}
g.Save();
image1.Dispose();
image2.Dispose();
image3.Dispose();
image4.Dispose();
}
using (var ms = new MemoryStream())
{
bg.Save(ms, ImageFormat.Png);
var buffers = ms.ToArray();
return ConvertToImage(buffers);
}
}
}
以下是用到的Helper方法:
/// <summary>
/// 下载图片
/// </summary>
/// <param name="imageUrl"></param>
/// <returns></returns>
public static byte[] Download(string imageUrl)
{
if (imageUrl.StartsWith("http"))
{
using (var ms = new MemoryStream())
{
var request = (HttpWebRequest)HttpWebRequest.Create(imageUrl);// 打开网络连接
using (var rs = request.GetResponse().GetResponseStream())// 向服务器请求,获得服务器的回应数据流
{
byte[] btArray = new byte[];// 定义一个字节数据,用来向readStream读取内容和向writeStream写入内容
int size = rs.Read(btArray, , btArray.Length);// 向远程文件读第一次 while (size > )// 如果读取长度大于零则继续读
{
ms.Write(btArray, , size);// 写入本地文件
size = rs.Read(btArray, , btArray.Length);// 继续向远程文件读取
}
return ms.ToArray();
}
}
}
else
{
using (var ms = new MemoryStream())
{
var img = Image.FromFile(imageUrl);
img.Save(ms, img.RawFormat);
return ms.ToArray();
}
}
} /// <summary>
/// 将byte数组转化为Image
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
public static Image ConvertToImage(byte[] buffer)
{
using (MemoryStream ms = new MemoryStream(buffer))
{
return Image.FromStream(ms);
}
} /// <summary>
/// 将Image转化为byte数组
/// </summary>
/// <param name="image"></param>
/// <returns></returns>
public static byte[] ConvertToByte(Image image)
{
using (MemoryStream ms = new MemoryStream())
{
image.Save(ms, image.RawFormat);
return ms.ToArray();
}
} /// <summary>
/// 等比缩放/放大成正方形图片
/// </summary>
/// <param name="orginal">原始图片</param>
/// <param name="size">目标宽高</param>
/// <param name="cute">超出部分是否剪裁</param>
/// <returns></returns>
public static Image ZoomToSqure(Image orginal, int size, bool cute = true)
{
var width = 0d;//图片宽度
var height = 0d;//图片高度
if (orginal.Width > orginal.Height)//原始图片宽度大于高度
{
height = size;
width = cute ? size : (orginal.Width * height / orginal.Height);
}
else if (orginal.Width < orginal.Height)//原始图片高度大于宽度
{
width = size;
height = cute ? size : (orginal.Height * width / orginal.Width);
}
else//原始图片是正方形,刚好
{
width = size;
height = size;
}
var board = new Bitmap((int)width, (int)height);
using (var g = Graphics.FromImage(board))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;//设置质量
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;//设置质量
g.Clear(Color.White);//置背景色
g.DrawImage(orginal, new Rectangle(, , board.Width, board.Height), new Rectangle(, , orginal.Width, orginal.Height), System.Drawing.GraphicsUnit.Pixel); //画图
orginal.Dispose();//释放原图
return board;
}
}
完整代码点击:https://github.com/night-king/ImageMerge
IM即时通讯群组头像拼接.net core 解决方案的更多相关文章
- 一行实现QQ群组头像,微信群组,圆角等效果. 并支持url直接加载图片
说点题外话. Coding中我们总是经历着这么几个过程. 学会使用: 不管是API也好, 开源库也好. 总是在最开始的学会去用. 了解实现原理: 可能会因为一些不兼容, 代码的异常状态的处理不够完美等 ...
- layim即时通讯实例各功能整合
一.系统演示1.1 聊天窗体主界面演示 1.2 模拟两人在线聊天(点击图片查看演示视频) 1.3 在线演示> 在线演示,点击进入系统到这里,若是您想要的,接下来听我娓娓道来二.开发工具开发软件: ...
- Activiti6.0 工作流引擎 websocket即时聊天发图片文字 好友群组 SSM源码
即时通讯:支持好友,群组,发图片.文件,消息声音提醒,离线消息,保留聊天记录 (即时聊天功能支持手机端,详情下面有截图) 工作流模块---------------------------------- ...
- java工作流引擎 Activiti6.0 websocket 即时聊天发图片文字 好友群组 SSM源码
时通讯:支持好友,群组,发图片.文件,消息声音提醒,离线消息,保留聊天记录 工作流模块--------------------------------------------------------- ...
- java ssm 后台框架平台 项目源码 websocket即时聊天发图片文字 好友群组 SSM源码
官网 http://www.fhadmin.org/D 集成安全权限框架shiro Shiro 是一个用 Java 语言实现的框架,通过一个简单易用的 API 提供身份验证和授权,更安全,更可靠E ...
- mui初级入门教程(五)— 聊聊即时通讯(IM),基于环信 web im SDK
文章来源:小青年原创发布时间:2016-06-15关键词:mui,环信 web im,html5+,im,页面传值,缓存转载需标注本文原始地址: http://zhaomenghuan.github. ...
- iOS:融云即时通讯快速集成
一.介绍 即时通讯在众多社交软件.生活软件以及教育软件中已经是必备的功能了,在当前国内,即时通讯SDK做的比较不错的有那么几家,例如环信SDK.融云SDK...,这两家做的都很不错,各有千秋吧,要是真 ...
- GIM企业即时通讯
GIM企业即时通讯是笔者Garfield(QQ:3674571)采用.NetFramework4.0+SQL2008R2开发的一套企业内网/外网 通用的即时通讯(IM)软件,分为服务器端和客户端,通讯 ...
- apicloud+融云实现即时通讯
请尊重作者的辛勤劳动!!! 使用apicloud开发已经快2个月了,起初的目的就是为了实现安卓和苹果的兼容,属于一个试验项目,究竟apicloud是否能够满足公司的要求?最 终看来还是不错的,使用ap ...
随机推荐
- js数据类型的检测总结,附面试题--封装一个函数,输入任意,输出他的类型
一.javascript 中有几种类型的值 1.基本数据类型 : 包括 Undefined.Null.Boolean.Number.String.Symbol (ES6 新增,表示独一无二的值) 特点 ...
- ogre3D学习基础3 -- 粒子与表层脚本
9.粒子脚本 粒子脚本允许你实例化地在你的脚本代码中定义粒子系统,而不必在源代码中进行设置,使得你做任何修改都能得到快速回应.脚本里定义的粒子系统被用作模板,并且多个实际的系统可以在运行时从这里被创建 ...
- nyoj 题目6 喷水装置
喷水装置(一) 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 现有一块草坪,长为20米,宽为2米,要在横中心线上放置半径为Ri的喷水装置,每个喷水装置的效果都会让以 ...
- [luogu2044][NOI2012] 随机数生成器 [矩阵快速幂]
题面: 传送门 思路: 看一眼这个公式: $x\left[n+1\right]=\left(a\ast x\left[n\right]+c\right) mod m$ 递推,数据范围$n\leq 10 ...
- cf 843 D Dynamic Shortest Path [最短路+bfs]
题面: 传送门 思路: 真·动态最短路 但是因为每次只加1 所以可以每一次修改操作的时候使用距离分层的bfs,在O(n)的时间内解决修改 这里要用到一个小技巧: 把每条边(u,v)的边权表示为dis[ ...
- PE508
真是日了苟了我之前还以为那个递归会炸状态..我真他妈胆小啊.. = = 明确一下,这个可以构成所有高斯整数(唯一),构造方法大概就是先看曼哈顿距离,然后判断要不要减1,然后再/(1-i) 我们考虑在末 ...
- T-SQL百万记录中分组取最大值方法ROW_NUMBER() OVER()
SELECT SysUserID, UserID, ROW_NUMBER() OVER(PARTITION BY UserID ORDER BY AddTime DESC) AS nums AND S ...
- 【TJOI2015】弦论 (后缀数组)
前言: 多好的题啊! 我理论$O(nlog_2n)$的后缀数组还带个常数26,竟然跑的比$O(n)$的后缀自动机还快,全场 Rak 1? Description 为了提高智商,ZJY开始学习弦论.这一 ...
- 【BZOJ3895】取石子(博弈,记忆化搜索)
题意: Alice和Bob两个好朋含友又开始玩取石子了.游戏开始时,有N堆石子排成一排,然后他们轮流操作(Alice先手),每次操作时从下面的规则中任选一个:1:从某堆石子中取走一个2:合并任意两堆石 ...
- VijosP1303 导弹拦截
背景 实中编程者联盟为了培养技术精湛的后备人才,必须从基础题开始训练. 描述 某国为了防御敌国的导弹袭击,研发出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度, ...