焦作赛VP打的非常一般,签完4道题之后,还有一个签到没签上,原因是有一个全局的queue忘记清空,由于那题输入过于繁琐,注意力没有集中在算法上,三个人都没看出,算是吸取了一个教训,而剩下有两道银牌题我都有思路,有一道sam我只想到了一半的思路,赛后看了吉老师的博客补完之后才发觉妙之又妙,学到了不少,而另外的一道也就是这道kdtree写出了几个bug,赛内时间剩下的太少没有调出来很可惜,遗憾下班。
不过因为是第一次写kdtree,赛后补了算是小有收获。

题意

题目大意是给你一个n*n(n<=3e5)的格子图,保证每行每列有且仅有一个棋子,输入是棋子的坐标,然后m(m<=3e5)个操作,有四种change和两种询问,change有L,R,U,D,四种类型,后面跟一个k,代表将 所有 棋子向左或者向其他方向移动k格,棋子本来就在这个方向上的边缘的话就保持不变。
第一种询问是 第x个棋子现在在哪个坐标。
第二种询问是 有多少对棋子合并到了一起(不重复计算,两个棋子合在一起只算一对)
题目链接:codeforces gym 102028 题目戳这

思路

1.被合并的棋子不可能再分离,这是我最开始想到的,显然作用不大,但再往下细想,如果说原始格子里的第一列棋子被合并到了第二列,那么它再也不可能回到原始格子内的第一列,这么说有些抽象,直接说我想到的一个主意好了,维护两个格子范围,一个是原始的格子范围,另一个是现在的格子范围,初始大小都为n*n,当现在的格子范围在移动之后如果超越了边界,那么它将被压缩,覆盖的范围就变少了,而原始的格子范围将会反向压缩,意味着越界的棋子被压缩到了原始格子还没有被压缩的地方,简单描述一下就是,原始格子范围内代表着还未被压缩的范围,现在格子范围内代表着剩下的棋子覆盖着的范围。

2.很抽象的描述了两个格子范围的意义,再很抽象的解释一下询问一的做法,询问一询问的是第x个棋子现在的坐标,这时维护的原始格子范围就起到了作用,如果这个棋子的x坐标在原始格子范围的上界和下界之外,那么直接将他的坐标压缩到对应的边界上,对y坐标和左界右界同理,这样就能得到这个棋子现在在原始格子的哪个位置,然后与现在格子范围对照一下,就能得到现在的坐标(很抽象,我实在懒得画图,要是不理解就自己画一下或者看代码qaq)

3.虽然标题写着kdtree但是好像上面都没有用到(标题党警告),不过询问二终究还是需要数据结构来维护,询问二询问的是合并的棋子的对数,不妨画个图移动移动,就可以简单的发现,因为每行每列最多只有一个棋子,所以合并的棋子一共最多会形成四堆,分别在左上左下和右上右下,这四个角可以和我刚刚描述的原始格子范围联系到一起,原始格子范围的左上角到整个格子的左上角,这个范围内的棋子会被压缩到一起,右上,左下右下同理,所以我们只要求出这四个范围内的棋子数量就好啦,经典kdtree的套路之一,但由于我从来没有使用过kdtree,所以赛内敲了claris的板子,由于不熟悉,有几个地方敲错了qaq,浪费了好多时间,大家引以为戒。
4.一点点小细节,维护格子范围的时候,维护四个边界就好,要注意当上下界碰到一起或者左右界碰到一起的时候要标记起来,因为这个时候只剩两个棋子堆了,还有全部合成一堆的情况,做两个标记就好。
可以看我写的丑陋代码啦qaq

#include<bits/stdc++.h>
using namespace std;
#define ford(i,a,n) for(i=a;i<=n;i++)
#define forb(i,n,a) for(i=n;i>=a;i--)
const int maxn=300010;
typedef long long ll;
struct node{int d[2],l,r,Max[2],Min[2],val,sum,f;
}t[maxn];
ll n;
int id[maxn],root,cmp_d;
bool cmp(const node &a,const node &b)
{return a.d[cmp_d]<b.d[cmp_d];
}
void umax(int&a,int b)
{if(a<b)a=b;
}
void umin(int&a,int b)
{if(a>b)a=b;
}
void up(int x)
{if(t[x].l){umax(t[x].Max[0],t[t[x].l].Max[0]);umin(t[x].Min[0],t[t[x].l].Min[0]);umax(t[x].Max[1],t[t[x].l].Max[1]);umin(t[x].Min[1],t[t[x].l].Min[1]);}if(t[x].r){umax(t[x].Max[0],t[t[x].r].Max[0]);umin(t[x].Min[0],t[t[x].r].Min[0]);umax(t[x].Max[1],t[t[x].r].Max[1]);umin(t[x].Min[1],t[t[x].r].Min[1]);}
}
int build(int l,int r,int D,int f)
{int mid=(l+r)>>1;cmp_d=D,nth_element(t+l+1,t+mid+1,t+r+1,cmp);id[t[mid].f]=mid;t[mid].f=f;t[mid].Max[0]=t[mid].Min[0]=t[mid].d[0];t[mid].Max[1]=t[mid].Min[1]=t[mid].d[1];t[mid].val=t[mid].sum=0;if(l!=mid) t[mid].l=build(l,mid-1,!D,mid);else t[mid].l=0;if(r!=mid) t[mid].r=build(mid+1,r,!D,mid);else t[mid].r=0;return up(mid),mid;
}
void change(int x,int p){for(t[x=id[x]].val+=p;x;x=t[x].f)t[x].sum+=p;
}
int k,X1,Y1,X2,Y2;
void ask(int x)
{if(t[x].Min[0]>X2||t[x].Max[0]<X1||t[x].Min[1]>Y2||t[x].Max[1]<Y1)return;if(t[x].Min[0]>=X1&&t[x].Max[0]<=X2&&t[x].Min[1]>=Y1&&t[x].Max[1]<=Y2){k+=t[x].sum;return;}if(t[x].d[0]>=X1&&t[x].d[0]<=X2&&t[x].d[1]>=Y1&&t[x].d[1]<=Y2)k+=t[x].val;if(t[x].l)ask(t[x].l);if(t[x].r)ask(t[x].r);
}
//以上全是claris的kdtree板子 ask用来询问矩形范围内的点权和,该题所有点点权为1,询问之前要将全局变量的x1,y1等等赋值,k清零
int oldx[maxn];
int oldy[maxn];//棋子坐标
ll getans(int n)
{return (ll)n*(n-1)/2;
}//n个棋子就是n*(n-1)/2堆
int main()
{//  ios::sync_with_stdio(false);
//  cin.tie(0);
//  cout.tie(0);int test;cin>>test;while(test--){int m;scanf("%lld%d",&n,&m);int i;ford(i,1,n){int x,y;scanf("%d%d",&x,&y);oldx[i]=x;oldy[i]=y;t[i].d[0]=x;t[i].d[1]=y;t[i].f=i;} root=build(1,n,0,0);//读入加建树 ford(i,1,n)change(i,1);//赋值点权值为1 ll nowl,nowr,nowu,nowd,oldl,oldr,oldu,oldd;//两个格子范围的四个边界 nowl=nowu=oldl=oldu=1;nowr=nowd=oldr=oldd=n;//一开始为全部 int flag1=0;int flag2=0;//标记两种边界是否碰到一起 while(m--){char op;op=getchar();while(op=='\n'||op==' ')op=getchar();if(op=='?')//询问1 {int num;scanf("%d",&num);int x=oldx[num];int y=oldy[num];if(y<oldl)y=oldl;if(y>oldr)y=oldr;if(x<oldu)x=oldu;if(x>oldd)x=oldd;//找到这个棋子在原始格子的坐标 int ansx=x-oldu+nowu;int ansy=y-oldl+nowl;//映射到现在格子的坐标 if(flag1)ansy=nowl;if(flag2)ansx=nowu;//判是否在线上(应该是没有作用的,但是我怕细节处理不到位就加了 printf("%d %d\n",ansx,ansy);}else if(op=='!'){if(flag1&&flag2){ll ans=getans(n);printf("%lld\n",ans);}//缩成一个点的话就是全部棋子的答案 else if(flag1)//压成一条竖线 {k=0;ll ans=0;X1=1,Y1=1,X2=oldu,Y2=n;//这里的查询范围一定要细之又细 谁写谁知道 ask(root);ans+=getans(k);k=0;X1=oldd,Y1=1,X2=n,Y2=n;//压成一条线要查两堆 ask(root);ans+=getans(k);printf("%lld\n",ans);}else if(flag2)//同上 {k=0;ll ans=0;X1=1,Y1=1,X2=n,Y2=oldl;ask(root);ans+=getans(k);k=0;X1=1,Y1=oldr,X2=n,Y2=n;ask(root);ans+=getans(k);printf("%lld\n",ans);}else//正常情况查四堆 {k=0;ll ans=0;X1=1,Y1=1,X2=oldu,Y2=oldl;ask(root);ans+=getans(k);k=0;X1=1,Y1=oldr,X2=oldu,Y2=n;ask(root);ans+=getans(k);k=0;X1=oldd,Y1=1,X2=n,Y2=oldl;ask(root);ans+=getans(k);k=0;X1=oldd,Y1=oldr,X2=n,Y2=n;ask(root);ans+=getans(k);printf("%lld\n",ans);}}else if(op=='L')//开始操作格子了 {int dis;scanf("%d",&dis);if(nowl<=dis)//如果左边界要被压的话 {int nowdis=dis-nowl+1;oldl+=nowdis;//原始矩阵要反向压 if(oldl>=oldr)flag1=1;//判断有没有成一条线 nowr-=dis;//右边界照动 nowl=1;//左边界动不了了 nowr=max(nowr,1ll);//右边界如果压出去了得压回来,就相当于压成一条线了 }else{nowl-=dis;nowr-=dis;//如果不被压的话,就正常操作一下,移动 }}else if(op=='R')//虽然同上,但是千万不要复制粘贴L的情况,照着自己思路重新写,慎重慎重!细节毁一生 {int dis;scanf("%d",&dis);if(nowr>=n-dis+1){int nowdis=nowr+dis-n;oldr-=nowdis;if(oldl>=oldr)flag1=1;nowl+=dis;nowr=n;nowl=min(nowl,n);}else{nowl+=dis;nowr+=dis;}}else if(op=='D'){int dis;scanf("%d",&dis);if(nowd>=n-dis+1){int nowdis=nowd+dis-n;oldd-=nowdis;if(oldu>=oldd)flag2=1;nowu+=dis;nowd=n;nowu=min(n,nowu);}else{nowu+=dis;nowd+=dis;}}else if(op=='U'){int dis;scanf("%d",&dis);if(nowu<=dis){int nowdis=dis-nowu+1;oldu+=nowdis;if(oldu>=oldd)flag2=1;nowd-=dis;nowu=1;nowd=max(1ll,nowd);}else{nowu-=dis;nowd-=dis;}}}}
}

完结撒花~

2018-2019ICPC焦作C题Supreme Command kd-tree相关推荐

  1. ICPC 2018 焦作 C题 Supreme Command

    ​​ 题意 给出一个n×nn×nn×n的棋盘,保证每行每列只有一个棋子,给出多种操作,将所有棋子先上/下/左/右移动kkk步(遇到边界则停止移动),提问某一个棋子的现在的位置或问当前有多少对棋子是在一 ...

  2. 后缀数组 ---- 2018~2019icpc焦作H题[后缀数组+st表+二分+单调栈]

    题目链接 题目大意: 给出nnn个数,定义f[l,r]f[l,r]f[l,r]表示 区间[l,r][l,r][l,r]的最大值,求所有 子区间的最大值的和,要求相同的子区间只能算一次 比如数列 5 6 ...

  3. Resistors in Parallel(Gym - 102028E 2018 ICPC 焦作E题 大数+规律C++版)

    链接:传送门 题外话:这套题作为队内训练赛,然后找规律找炸了,后来补题,发现大家都在用java或者python写,太难了,不会啊,只会C++的萌新躲在墙角瑟瑟发抖,写下了这个C++版本的解题报告 题意 ...

  4. HDU2966 In case of failure(浅谈k-d tree)

    嘟嘟嘟 题意:给定\(n\)个二维平面上的点\((x_i, y_i)\),求离每一个点最近的点得距离的平方.(\(n \leqslant 1e5\)) 这就是k-d tree入门题了. k-d tre ...

  5. 2018深信服java笔试题,深信服2018秋招编程题

    深信服2018秋招编程题 深信服2018秋招编程题 第一题: 其中,reverse函数的代码实现如下: 1. unsigned int reverse(unsigned int num) { unsi ...

  6. 京东2018秋招编程题

    C++开发工程师 京东 2018秋招编程题 (代码后续更新...) 1.神奇数 将一个数n的各数位分成两部分,两部分之和相等,则称这个数为神奇数.比如242分成[2,2].[4].输入一个范围[l, ...

  7. 滴滴2018秋招编程题

    滴滴 2018秋招 编程题 1.寻找丑数 丑数的定义是,只包含因子2.3和5的数称作丑数.比如6和8是丑数,14不是丑数,因为含有因子7.输入一个整数n,输出第n个丑数.我们认为第一个丑数是1. 解析 ...

  8. 拼多多2018年校招真题

    拼多多2018年校招真题 题目描述 给定一个无序数组,包含正数.负数和0,要求从中找出3个数的乘积,使得乘积最大,要求时间复杂度:O(n),空间复杂度:O(1) 输入描述: 无序整数数组A[n] 输出 ...

  9. 魔法币 java_网易2018校招笔试编程题-魔法币 java实现

    魔法币 原题 小易准备去魔法王国采购魔法神器,购买魔法神器需要使用魔法币,但是小易现在一枚魔法币都没有,但是小易有两台魔法机器可以通过投入x(x可以为0)个魔法币产生更多的魔法币. 魔法机器1:如果投 ...

最新文章

  1. OpenCV-Java版学习(1.在IDEA中使用OpenCV)
  2. mysql-connector-java.jar乱码_jdbc连接数据库,中文出现乱码的问题
  3. 手把手教你用Spring Cloud和Docker构建微服务
  4. Oracle ODI 12c之多表联合查询以及定时任务设置
  5. 打印机可以打印不能扫描怎么弄_惠普打印机可以复印不能扫描怎么操作
  6. OTA升级常见问题及流程
  7. Nokia5233手机和我装的几个symbian V5手机软件
  8. 完美体验 微软WP7智能手机七大功能亮点
  9. commbean java_Oracle Java 7 JmxMBeanServer类远程代码执行漏洞
  10. rpm和yum命令的区别
  11. 公路车sava和Java_入门之作 意外惊喜 SAVA追风5.0公路车 评测
  12. 文本文件和二进制文件
  13. 汇洁集团牵手阿里云,开启内衣服饰企业数字化转型
  14. VMP (VMProtect)脱壳
  15. fireworks快捷键
  16. 大话设计模式之爱你一万年:第七章 结构型模式:装饰器模式:爱你就要让你更美丽:为爱找份工作:1.装饰器模式概念
  17. Android MTK 音频通道修改
  18. SQL的内连接与外连接
  19. 使用Java及jsoup爬取链家北京二手房房价数据
  20. 苹果MAC OS 系统 安装指南 手册 步骤

热门文章

  1. mysql数据库存储引擎
  2. asp.net 设计音乐网站
  3. 【NOIP2014】生活大爆炸版石头剪刀布
  4. 为什么 DNS 使用 UDP 协议
  5. 泰勒公式和麦克劳林公式
  6. Python 位操作符(Bitwise)
  7. 【泰国留学】为什么“内卷”是必然,这就是原因
  8. Arranging Wine
  9. 微软自动调参工具—NNI安装与快速上手,AutoML必备工具
  10. 安徽省大数据与人工智能竞赛经验分享-3【从赛题中分析比赛需要的技能】