前言

如今触屏设备越来越流行,并且大多数已经支持html5了。针对此。对触屏设备开发图片裁剪功能,

让其能够直接处理图片。减轻服务端压力。

技术点

浏览器必须支持html5,包含fileReader。canvas等api,而且该设备至少支持单点触事件(touchstart,touchmove,touchend),可惜的是

非常多浏览器仅仅能识别一仅仅手指(不支持多点触摸事件,假如支持的话,请告知我)。

思路

利用filereader直接读取本地图片。然后赋予一个图片。该图片及裁剪框的位置计算跟pc端一样,可是触发的事件不一样,触屏版是依据触屏事件触发的。裁剪时,利用cavas的api直接画出相关图像,然后得到数据。再利用xmlhttprequest发送请求。

非html5无法完毕这个过程。

执行结果

这仅仅是一个demo,也是最初的雏形,当然不会太好看了,可是基本实现功能就可以。



部分代码


  1. <!doctype html>
  2. <html>
  3.  
  4. <head>
  5. <meta name="Author" content="flashlizi - www.riaidea.com">
  6. <meta name="Description" content="HTML5 experiment">
  7. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  8. <title>头像上传组件 - HTML5版</title>
  9. <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
  10.  
  11. <style>
  12. body
  13. {
  14. padding: 0;
  15. margin: 0;
  16. height: 100%;
  17. background-color: #eee;
  18. font-size: 12px;
  19. color: #666;
  20. }
  21.  
  22. a
  23. {
  24. text-decoration: none;
  25. color: #333;
  26. }
  27.  
  28. a:hover
  29. {
  30. text-decoration: none;
  31. color: #f00;
  32. }
  33.  
  34. </style>
  35. <script>
  36.  
  37. if(window.FileReader==undefined){
  38. alert("该手机不支持html5");
  39. }
  40.  
  41. </script>
  42.  
  43. <script type="text/javascript" src="/static/mobile/lib/zepto.min.js"></script>
  44. </head>
  45.  
  46. <body >
  47. <h1>选择图片:<input type="file" id="browseFile" onchange=""><input type="button" id="saveimg" value="保存图片"/></h1>
  48. <div id="wrapper" style="border: 1px gray dotted; padding: 25px;">
  49.  
  50. <div id="component_box" style="position: relative; border: 1px green solid; width: 300px; height: 300px;">
  51. <div id="tipBox" style="display: none;">
  52. <img src=""/>
  53. </div>
  54. <div id="mainCutter" style="overflow: hidden; display: none; position: relative;">
  55. <img id="imgPreview" />
  56. <div id="cutBox" style=" position:absolute; width: 150px; height: 200px; opacity: 0.5; background: gray;"></div>
  57. </div>
  58. </div>
  59.  
  60. <!--画布-->
  61.  
  62. <canvas id="cropper" style=" display:none;border:1px solid red; width: 300px; height: 300px;" ></canvas>
  63. </div>
  64. <div><span style="color: green;">调整裁剪区域大小:</span>
  65. <!--调整用slider-->
  66. <div><div id="processBar" style=" margin: 0 auto; position: relative; width: 220px; height: 20px; background: green;"><div id="processPoint" style="background: url(images/horizSlider.png); width: 18px; height: 20px; position: absolute;left: 0;top: 0;"></div></div></div>
  67. </div>
  68. <div id="the_show" style="display: none;">
  69. <h2>提示:</h2>
  70. <div id="theTips"></div>
  71.  
  72. <h2>后台获得的图像</h2>
  73. <img src="" id="showImg"/>
  74. </div>
  75. <div style="color: green;">友情提醒:拖动裁剪框裁剪框将随之移动,上划放大裁剪框,下滑缩小裁剪框。</div>
  76. <div id="tips2" style="color: green; position: absolute;left: 0px; bottom: 0px; border: 1px solid green;"></div>
  77.  
  78. <script type="text/javascript">
  79. //--逻辑。点击图片上传选择后将载入预览图片
  80. var Options={
  81. width:300,
  82. height:300,
  83. cutWidth:150,
  84. cutHeight:200,
  85. cutMinSize:50,//裁剪框最小尺寸,即最小能够缩放到这个size,width及height随意一个都无法小于这个值。
  86.  
  87. //--系统自带。执行时自己主动运算,请不要改动。
  88.  
  89. cropViewWidth:0,//在画布里面显示的最大宽度
  90. cropViewHeight:0,//在画布里面显示的最大高度
  91. cropLeft:0,
  92. cropTop:0,
  93. //--裁剪框
  94. cutViewWidth:0, //当前宽度,
  95. cutViewHeight:0,//当前高度
  96. cutMaxWidth:0, //裁剪框最大宽度。
  97. cutMaxHeight:0,//裁剪框最大高度。
  98.  
  99. //--四象限。用于推断距离。
  100. cutBoxLimitX1:0,
  101. cutBoxLimitX2:0,
  102. cutBoxLimitY1:0,
  103. cutBoxLimitY2:0,
  104. cutLeft:0,//裁剪框绝对定位,左側距离。
  105.  
  106. cutTop:0,//裁剪框绝对定位。离顶部距离。
  107.  
  108. initStatus:false//当前组件是否已经初始化了。
  109. };
  110. var Options_image={
  111. width:0,
  112. height:0,
  113. imgData:""
  114. }
  115.  
  116. var input_browseFile = document.getElementById("browseFile");
  117. var img_preview = document.getElementById("imgPreview");
  118. var cutBox=document.getElementById("cutBox");
  119. var tipBox=document.getElementById("tipBox");
  120. var _cropper=document.getElementById("cropper");
  121. var mainCutter=document.getElementById("mainCutter");
  122. var tips2=$("#tips2");
  123. var wrapper=document.getElementById("wrapper");
  124. var component_box=document.getElementById("component_box");
  125.  
  126. var ctx = _cropper.getContext('2d');//ctx.drawImage(myImage, 50, 50);
  127. function previewInImage (file) {
  128. //通过file.size能够取得图片大小
  129. var reader = new FileReader();
  130. LoadingImage();
  131.  
  132. reader.onload = function( evt ){
  133. img_preview.src = evt.target.result;
  134. }
  135. Options_image.imgData= reader.readAsDataURL(file);
  136. }
  137. img_preview.onload=function(){
  138. Options_image.width=img_preview.width;
  139. Options_image.height=img_preview.height;
  140. _initCropAndCut();
  141. }
  142. function LoadingImage(){
  143. $(img_preview).css({"width":"","height":""});
  144. }
  145. function _initCropAndCut(){
  146. //--计算比例。将其放到canvas里面。
  147.  
  148. var scale = Math.max(Options_image.width/Options.width,Options_image.height/Options.height);
  149. if(scale>1){
  150. Options.cropViewWidth=parseInt(Math.floor(Options_image.width/scale));
  151. Options.cropViewHeight=parseInt(Math.floor(Options_image.height/scale));
  152. }
  153. else{
  154. Options.cropViewWidth=Options_image.width;
  155. Options.cropViewHeight=Options_image.height;
  156. }
  157. //--计算画布里面的图像的位置。
  158. Options.cropLeft=parseInt((Options.width-Options.cropViewWidth)/2);
  159. Options.cropTop=parseInt((Options.height-Options.cropViewHeight)/2);
  160. //--计算裁剪框实际大小及实际位置。
  161. //计算裁剪框的位置。
  162.  
  163. var scale_2=Math.max(Options.cutWidth/Options.cropViewWidth,Options.cutHeight/Options.cropViewHeight);
  164. if(scale_2>1){
  165. Options.cutViewWidth=parseInt(Math.floor(Options.cutWidth/scale_2));
  166. Options.cutViewHeight=parseInt(Math.floor(Options.cutHeight/scale_2));
  167. }
  168. else{
  169. Options.cutViewHeight=Options.cutHeight;
  170. Options.cutViewWidth=Options.cutWidth;
  171. }
  172. Options.cutMaxWidth=Options.cutViewWidth;
  173. Options.cutMaxHeight=Options.cutViewHeight;
  174.  
  175. Options.cutLeft=parseInt(Math.floor((Options.cropViewWidth-Options.cutViewWidth))/2);
  176. Options.cutTop=parseInt(Math.floor((Options.cropViewHeight-Options.cutViewHeight))/2);
  177. //-四象限。
  178. Options.cutBoxLimitX1=0;
  179. Options.cutBoxLimitX2=Options.cropViewWidth;
  180. Options.cutBoxLimitY1=0;
  181. Options.cutBoxLimitY2=Options.cropViewHeight;
  182.  
  183. $(cutBox).css({"display":"block","width":Options.cutViewWidth+"px","height":Options.cutViewHeight+"px","left":Options.cutLeft+"px","top":Options.cutTop+"px"});
  184. $(img_preview).css({"width":Options.cropViewWidth+"px","height":Options.cropViewHeight+"px"});
  185. $(mainCutter).css({"display":"block","width":Options.cropViewWidth+"px","height":Options.cropViewHeight+"px","left":Options.cropLeft+"px","top":Options.cropTop+"px"});
  186. //ctx.drawImage(img_preview,Options.cropLeft,Options.cropTop,Options.cropViewWidth,Options.cropViewHeight);
  187. //ctx.drawImage(img_preview, 0, 0, Options_image.width,Options_image.height, Options.cropLeft, Options.cropTop, Options.cropViewWidth, Options.cropViewHeight );
  188.  
  189. Options.initStatus=true;
  190. Options_process.initStatus=true;
  191. Options_process.percent=100;
  192. Options_process.pointX=Options_process.barWidth;
  193. _resizeProcessBar();
  194. }
  195.  
  196. input_browseFile.addEventListener("change", function () {
  197. //通过 this.files 取到 FileList ,这里仅仅有一个
  198. previewInImage(this.files[0]);
  199.  
  200. }, false);
  201. //--加入缩放功能。
  202.  
  203. Options_zoom={
  204. beginX1:0,
  205. beginY1:0,
  206. beginX2:0,
  207. beginY2:0,
  208. endX1:0,
  209. endY1:0,
  210. endX2:0,
  211. endY2:0
  212. };
  213. //--加入裁剪框移动功能
  214. Options_move={
  215. beginX1:0,
  216. beginY1:0,
  217. endX1:0,
  218. endY1:0
  219. };
  220.  
  221. /**
  222. * 拖动裁剪框的逻辑处理。
  223. * */
  224. cutBox.addEventListener("touchstart",function(event){
  225. event.preventDefault();
  226. event.stopPropagation();
  227. Options_move={
  228. beginX1:0,
  229. beginY1:0,
  230. endX1:0,
  231. endY1:0
  232. };
  233. var beginX=event.changedTouches[0].pageX;
  234. var beginY=event.changedTouches[0].pageY;
  235. Options_move.beginX1=beginX;
  236. Options_move.beginY1=beginY;
  237.  
  238. },false);
  239. cutBox.addEventListener("touchmove",function(event){
  240. event.preventDefault();
  241. event.stopPropagation();
  242. //--
  243. var beginX=event.changedTouches[0].pageX;
  244. var beginY=event.changedTouches[0].pageY;
  245. Options_move.endX1=beginX;
  246. Options_move.endY1=beginY;
  247. //--计算是否发生位移,依据位移来定位裁剪框位置。
  248.  
  249. //位移量。
  250. var _d_x=Options_move.endX1-Options_move.beginX1;
  251. var _d_y=Options_move.endY1-Options_move.beginY1;
  252. //--当前裁剪框原始位置。
  253.  
  254. var _new_x=Options.cutLeft;
  255. var _new_y=Options.cutTop;
  256. _new_x+=_d_x;
  257. _new_y+=_d_y;
  258. //--推断是否在矩形边框,假如超出去,那么就取终于点。
  259. //--注意,推断相关点的范围。
  260.  
  261. if(_new_x<Options.cutBoxLimitX1){
  262. _new_x=Options.cutBoxLimitX1;
  263. }
  264. else if(_new_x>Options.cutBoxLimitX2){
  265. _new_x=Options.cutBoxLimitX2;
  266. }
  267. //--顺便推断。加上宽度后,是否超过了范围。
  268.  
  269. if((_new_x+Options.cutViewWidth)>Options.cutBoxLimitX2){
  270. _new_x=Options.cutBoxLimitX2-Options.cutViewWidth;
  271. }
  272. if(_new_y<Options.cutBoxLimitY1){
  273. _new_y=Options.cutBoxLimitY1;
  274. }
  275. else if(_new_y>Options.cutBoxLimitY2){
  276. _new_y=Options.cutBoxLimitY2;
  277. }
  278. //--顺便推断,加上裁剪框高度后,是否超过下限。
  279. if((_new_y+Options.cutViewHeight)>Options.cutBoxLimitY2){
  280. _new_y=Options.cutBoxLimitY2-Options.cutViewHeight;
  281. }
  282.  
  283. Options.cutLeft=_new_x;
  284. Options.cutTop=_new_y;
  285. _resizeCutBox();
  286. //---将这一点的放回前一点。
  287. Options_move.beginX1=Options_move.endX1;
  288. Options_move.beginY1=Options_move.endY1;
  289.  
  290. },false);
  291. cutBox.addEventListener("touchend",function(event){
  292. event.preventDefault();
  293. event.stopPropagation();
  294. return;
  295.  
  296. },false);
  297. /**
  298. * 依据相关參数又一次resize裁剪框
  299. * */
  300. function _resizeCutBox(){
  301. $(cutBox).css({"width":Options.cutViewWidth+"px","height":Options.cutViewHeight+"px","left":Options.cutLeft+"px","top":Options.cutTop+"px"});
  302. }
  303. function _getCutImageData(){
  304. var output = document.createElement("canvas");
  305. //--坐标换算。
  306. var scale_x=Options_image.width/Options.cropViewWidth;
  307. var scale_y=Options_image.height/Options.cropViewHeight;
  308. var _o_x=parseInt( (scale_x)*Options.cutLeft);
  309. var _o_y=parseInt( (scale_y)*Options.cutTop);
  310. //--长度换算
  311. var _o_width=parseInt(scale_x*Options.cutViewWidth);
  312. var _o_height=parseInt(scale_y*Options.cutViewHeight);
  313.  
  314. output.width = Options.cutWidth;
  315. output.height = Options.cutHeight;
  316. output.getContext("2d").drawImage(img_preview, _o_x,_o_y, _o_width, _o_height, 0, 0, output.width, output.height);
  317. return output.toDataURL("image/jpeg");
  318. }
  319. function saveImage()
  320. {
  321. var imgData = _getCutImageData();
  322. /*
  323.  
  324. $("#the_show").css("display","block");
  325.  
  326. document.getElementById("showImg").src=imgData;
  327. return;
  328. */
  329. var xhr = new XMLHttpRequest();
  330. xhr.onreadystatechange = function(e)
  331. {
  332. if(xhr.readyState == 4)
  333. {
  334. if(xhr.status == 200)
  335. {
  336. //--获取返回的数据。
  337. var _res=xhr.responseText;
  338. _res= $.trim(_res);
  339. var json= $.parseJSON(_res);
  340. if(json.status==true){
  341. $("#the_show").css("display","block");
  342. var surl=json.url+"?t="+Math.random();
  343. $("#showImg").attr("src",surl);
  344. }
  345. else{
  346. alert(json.message);
  347. }
  348. //document.getElementById("status").innerHTML = "<font color='#f00'>上传成功!</font>";
  349. }
  350. else{
  351. alert("服务端无法响应,错误编号:"+xhr.status);
  352.  
  353. }
  354. }
  355. };
  356.  
  357. xhr.open("post", "/quickTest/html5CropperHandler.jsp", true);
  358. var data = new FormData();
  359. data.append("username", "flashlizi");
  360. data.append("size", 180);
  361. data.append("file", imgData);
  362. xhr.send(data);
  363. }
  364. /**
  365. * processBar 进度条相关操作。
  366. * */
  367.  
  368. Options_process={
  369. beginX:0,//触摸时候起始点
  370. beginY:0,//触摸时候起始点
  371. endX:0,//触摸时候终点
  372. endY:0,//触摸时候终点
  373. barWidth:200,//进度条长度
  374. pointX:0,//当前指示点位置
  375. pointY:0,
  376. percent:0,//百分比值。
  377. initStatus:false
  378. };
  379. var processBar=document.getElementById("processBar");
  380. var processPoint=document.getElementById("processPoint");
  381.  
  382. //--加入触屏事件,监控相关动作。
  383. //開始触摸
  384. processBar.addEventListener("touchstart",function(event){
  385. event.preventDefault();
  386. event.stopPropagation();
  387.  
  388. if(!Options_process.initStatus){
  389. return;
  390. }
  391. var beginX=event.changedTouches[0].pageX;
  392. var beginY=event.changedTouches[0].pageY;
  393. Options_process.beginX=beginX;
  394. Options_process.beginY=beginY;
  395. },false) ;
  396. //--移动中
  397. processBar.addEventListener("touchmove",function(event){
  398. event.preventDefault();
  399. event.stopPropagation();
  400.  
  401. if(!Options_process.initStatus){
  402. return;
  403. }
  404. var beginX=event.changedTouches[0].pageX;
  405. var beginY=event.changedTouches[0].pageY;
  406. Options_process.endX=beginX;
  407. Options_process.endY=beginY;
  408. //--计算比分比。
  409.  
  410. var _d_x=Options_process.endX-Options_process.beginX;
  411. Options_process.percent+=parseInt(_d_x*100/Options_process.barWidth);
  412. if(Options_process.percent<0){
  413. Options_process.percent=0;
  414. }
  415. else if(Options_process.percent>100){
  416. Options_process.percent=100;
  417. }
  418. //--计算那个指示点位置。
  419.  
  420. Options_process.pointX=parseInt(Options_process.barWidth*(Options_process.percent/100));
  421. _resizeProcessBar();
  422. //--依据百分比,设置裁剪框大小。
  423. var _o_cut_x=Options.cutLeft;
  424. var _o_cut_y=Options.cutTop;
  425. var _o_cut_width=Options.cutViewWidth;
  426. var _new_cut_width= parseInt(Options.cutMaxWidth*(Options_process.percent/100));
  427. var _new_cut_height= parseInt(Options.cutMaxHeight*(Options_process.percent/100));
  428. if(_new_cut_width>_o_cut_width){
  429. //--扩大了。
  430. //--计算当前坐标
  431. var _d_x_2=_new_cut_width-Options.cutViewWidth;
  432. var _d_y_2=_new_cut_height-Options.cutViewHeight;
  433.  
  434. Options.cutLeft=Options.cutLeft-parseInt(_d_x_2/2);
  435. Options.cutTop=Options.cutTop-parseInt(_d_y_2/2);
  436. Options.cutViewWidth=_new_cut_width;
  437. Options.cutViewHeight=_new_cut_height;
  438. _resizeCutBox();
  439.  
  440. }
  441. else if(_new_cut_width<_o_cut_width){
  442. //--缩小了。
  443.  
  444. var _d_x_2=Options.cutViewWidth-_new_cut_width;
  445. var _d_y_2=Options.cutViewHeight-_new_cut_height;
  446. Options.cutLeft=Options.cutLeft+parseInt(_d_x_2/2);
  447. Options.cutTop=Options.cutTop+parseInt(_d_y_2/2);
  448. Options.cutViewWidth=_new_cut_width;
  449. Options.cutViewHeight=_new_cut_height;
  450. _resizeCutBox();
  451.  
  452. }
  453.  
  454. //--兴许处理。
  455. Options_process.beginX=Options_process.endX;
  456. Options_process.endY=Options_process.endY;
  457.  
  458. },false) ;
  459. //--结束
  460. processBar.addEventListener("touchend",function(event){
  461. event.preventDefault();
  462. event.stopPropagation();
  463.  
  464. if(!Options_process.initStatus){
  465. return;
  466. }
  467. },false) ;
  468. /**
  469. * 依据相关属性。重设slider位置。
  470. * */
  471. function _resizeProcessBar(){
  472. $(processPoint).css("left",Options_process.pointX+"px");
  473. }
  474.  
  475. $("#saveimg").click(function(){
  476. if(Options.initStatus==false){
  477. alert("请先选择图片!");
  478. return;
  479. }
  480. saveImage();
  481. });
  482.  
  483. </script>
  484.  
  485. </body>
  486. </html>


在触屏设备上面利用html5裁剪图片的更多相关文章

  1. 在触屏设备上面利用html5裁剪图片(转)

    前言 现在触屏设备越来越流行,而且大多数已经支持html5了.针对此,对触屏设备开发图片裁剪功能, 让其可以直接处理图片,减轻服务端压力. 技术点 浏览器必须支持html5,包括fileReader, ...

  2. jquery -- 触屏设备touch事件

    几种普及得比较好的触摸事件,你可以在绝大多数现代浏览器中来测试这一事件(必须是触屏设备哦): touchstart:触摸开始的时候触发 touchmove:手指在屏幕上滑动的时候触发 touchend ...

  3. 在触屏设备中拖动 overflow 元素

    在 Android 和 iOS 等触屏设备中,如果网页中某元素设置 overflow: auto 或者 overflow:scroll,那么问题就来了.在 Android 3.0 之前以及 iPhon ...

  4. HTML5裁剪图片并上传至服务器实现原理讲解

    HTML5裁剪图片并上传至服务器实现原理讲解   经常做项目需要本地上传图片裁剪并上传服务器,比如会议头像等功能,但以前实现这类需求都很复杂,往往需要先把图片上传到服务器,然后返回给用户,让用户确定裁 ...

  5. [Winform]关于cefsharp触屏设备长按文本内容,崩溃问题的修复

    摘要 在之前遇到cefsharp,在触屏电脑上,长按文本内容,会崩溃的问题. 相关文章 当时遇到这样的问题,在cefsharp项目下提交了bug.已经修复,可以参考当时我提的bug,以及解决过程,可参 ...

  6. 触屏设备上的多点触碰检测C++代码实现

    转自:http://aigo.iteye.com/blog/2272698 代码还是参考自Epic官方的塔防项目:StrategyGame 看了下C++的API,现成的API中貌似只支持单点触碰检测, ...

  7. 让BOOTSTRAP默认SLIDER支持触屏设备

    var isTouch=('ontouchstart' in window); if(isTouch){ $(".carousel").on('touchstart', funct ...

  8. iOS 利用Context裁剪图片

    下面的代码可以裁剪出圆形的图片, 1,先把不规则图片转成正方形图片 UIGraphicsBeginImageContext(newSize); [image drawInRect:CGRectMake ...

  9. 利用html5压缩图片,产出base64图片

    /* 将页面选择的图片等比压缩成指定大小(长边固定) file:图片文件 callBack:回调函数 maxLen:长边的长度*/function makePic(file,callBack,maxL ...

随机推荐

  1. Virut.ce-感染型病毒分析报告

    1.样本概况 病毒名称 Virus.Win32.Virut.ce MD5 6A500B42FC27CC5546079138370C492F 文件大小 131 KB (134,144 字节) 壳信息 无 ...

  2. JavaScript——创建对象

    <script type="text/javascript"> //声明变量的首字母是小写 //1.对象字面量 /*var person = { name:" ...

  3. InteliJ IDEA 简单使用:配置项目所需jdk

    1:配置项目所需jdk: File->Project Structure 弹出如下界面: 首先选中SDKs,会出现下图界面:点击“+”标志弹出Add New SDK 然后选择JDK,会弹出路径框 ...

  4. list的几种new方式比较ImmutableList

    数组的非空判断: -----数组的非空判断----- StringUtils.isNotBlank(array); list的非空判断: -----list的非空判断----- CollectionU ...

  5. js字符串操作之substr与substring

    substr和substring两个都是截取字符串的. 两者有相同点,如果只是写一个参数,两者的作用都是一样的:就是截取字符串当前下标以后直到字符串最后的字符串片段. 例如: `var a=" ...

  6. Laravel中服务提供者和门面模式

    在laravel中,我们可能需要用到自己添加的类时,可以建立一个文件夹专门存放类文件,也可以使用laravel的服务提供者的方式来使用. 这两者其实区别不大,主要是前者使用的话,会跟业务代码产生依赖, ...

  7. 2019寒假练题计划——LibreOJ刷题计划 &《信息学奥赛一本通》提高版题目

    目录 2019.1.27 #10082. 「一本通 3.3 例 1」Word Rings 题意 思路 #10083. 「一本通 3.3 例 2」双调路径 题意 思路 #10084. 「一本通 3.3 ...

  8. rpm包软件管理

    一.rpm介绍 linux服务器中所有的软件包安装方式有两种,一种是源码安装.另一种是二进制包安装(rpm)源码包安装的好处是适合不同的发行版本的linux,缺点是在编译过程中花费的时间很长,二进制包 ...

  9. 【LOJ】#2479. 「九省联考 2018」制胡窜

    题解 老了,国赛之前敲一个后缀树上LCT和线段树都休闲的很 现在后缀树上线段树合并差点把我写死 主要思路就是后缀树+线段树合并+容斥,我相信熟练的OIer看到这已经会了 但就是不想写 但是由于我过于老 ...

  10. 【LOJ】#2126. 「HAOI2015」数组游戏

    题解 简单分析一下就知道\(\lfloor \frac{N}{i} \rfloor\)相同的\(i\)的\(sg\)函数相同 所以我们只要算\(\sqrt{n}\)个\(sg\)函数就好 算每一个\( ...