2018-2019ICPC焦作C题Supreme Command kd-tree
焦作赛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相关推荐
- ICPC 2018 焦作 C题 Supreme Command
题意 给出一个n×nn×nn×n的棋盘,保证每行每列只有一个棋子,给出多种操作,将所有棋子先上/下/左/右移动kkk步(遇到边界则停止移动),提问某一个棋子的现在的位置或问当前有多少对棋子是在一 ...
- 后缀数组 ---- 2018~2019icpc焦作H题[后缀数组+st表+二分+单调栈]
题目链接 题目大意: 给出nnn个数,定义f[l,r]f[l,r]f[l,r]表示 区间[l,r][l,r][l,r]的最大值,求所有 子区间的最大值的和,要求相同的子区间只能算一次 比如数列 5 6 ...
- Resistors in Parallel(Gym - 102028E 2018 ICPC 焦作E题 大数+规律C++版)
链接:传送门 题外话:这套题作为队内训练赛,然后找规律找炸了,后来补题,发现大家都在用java或者python写,太难了,不会啊,只会C++的萌新躲在墙角瑟瑟发抖,写下了这个C++版本的解题报告 题意 ...
- HDU2966 In case of failure(浅谈k-d tree)
嘟嘟嘟 题意:给定\(n\)个二维平面上的点\((x_i, y_i)\),求离每一个点最近的点得距离的平方.(\(n \leqslant 1e5\)) 这就是k-d tree入门题了. k-d tre ...
- 2018深信服java笔试题,深信服2018秋招编程题
深信服2018秋招编程题 深信服2018秋招编程题 第一题: 其中,reverse函数的代码实现如下: 1. unsigned int reverse(unsigned int num) { unsi ...
- 京东2018秋招编程题
C++开发工程师 京东 2018秋招编程题 (代码后续更新...) 1.神奇数 将一个数n的各数位分成两部分,两部分之和相等,则称这个数为神奇数.比如242分成[2,2].[4].输入一个范围[l, ...
- 滴滴2018秋招编程题
滴滴 2018秋招 编程题 1.寻找丑数 丑数的定义是,只包含因子2.3和5的数称作丑数.比如6和8是丑数,14不是丑数,因为含有因子7.输入一个整数n,输出第n个丑数.我们认为第一个丑数是1. 解析 ...
- 拼多多2018年校招真题
拼多多2018年校招真题 题目描述 给定一个无序数组,包含正数.负数和0,要求从中找出3个数的乘积,使得乘积最大,要求时间复杂度:O(n),空间复杂度:O(1) 输入描述: 无序整数数组A[n] 输出 ...
- 魔法币 java_网易2018校招笔试编程题-魔法币 java实现
魔法币 原题 小易准备去魔法王国采购魔法神器,购买魔法神器需要使用魔法币,但是小易现在一枚魔法币都没有,但是小易有两台魔法机器可以通过投入x(x可以为0)个魔法币产生更多的魔法币. 魔法机器1:如果投 ...
最新文章
- OpenCV-Java版学习(1.在IDEA中使用OpenCV)
- mysql-connector-java.jar乱码_jdbc连接数据库,中文出现乱码的问题
- 手把手教你用Spring Cloud和Docker构建微服务
- Oracle ODI 12c之多表联合查询以及定时任务设置
- 打印机可以打印不能扫描怎么弄_惠普打印机可以复印不能扫描怎么操作
- OTA升级常见问题及流程
- Nokia5233手机和我装的几个symbian V5手机软件
- 完美体验 微软WP7智能手机七大功能亮点
- commbean java_Oracle Java 7 JmxMBeanServer类远程代码执行漏洞
- rpm和yum命令的区别
- 公路车sava和Java_入门之作 意外惊喜 SAVA追风5.0公路车 评测
- 文本文件和二进制文件
- 汇洁集团牵手阿里云,开启内衣服饰企业数字化转型
- VMP (VMProtect)脱壳
- fireworks快捷键
- 大话设计模式之爱你一万年:第七章 结构型模式:装饰器模式:爱你就要让你更美丽:为爱找份工作:1.装饰器模式概念
- Android MTK 音频通道修改
- SQL的内连接与外连接
- 使用Java及jsoup爬取链家北京二手房房价数据
- 苹果MAC OS 系统 安装指南 手册 步骤