\(\newcommand{\RNum}[1]{\uppercase\expandafter{\romannumeral #1\relax}}\)
T1
一道可以暴力撵标算的题……
Description
- 给定二维平面上\(N(≤60000)\)个有权值的点\((X_i,Y_i)(\in[0,10^9])\),点权\(Z_i\in[0,10^9]\)。
- 有\(M(≤10000)\)次操作,操作\(Ⅰ\)是询问一个边平行于坐标轴的矩形中权值第\(K\)小的点,操作\(Ⅱ\)是交换两个点的权值。
时限10s。
SolutionⅠ
- 既然时限10s,那肯定要想到暴力。
- 最朴素暴力应该是找出所有在矩形中的点,快排,取第\(K\)小。这样是\(O(NM\log_2N)\)的,实测会\(TLE\),只有52.4pts。
考虑\(O(N)\)找第\(K\)小。可以先将权值离散化,然后把快排改为桶牌,这样就变成\(O(N\log_2N+NM)\)了。直接交似乎是3~4s;吸臭氧后可以达到1s+。
Code
#include <cstdio>
#include <algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define O3 __attribute((optimize("-O3")))
#define NO {puts("It doesn't exist."); continue;}
using namespace std;
const int N=81e3;
int i,j,n,m,n1,Z[N],pos[N],x,y,x1,y1,k,l,r,mid,cnt[N],ss;
struct city{int x,y,z,i;}a[N];
O3 void read(int&x)
{
char c=getchar(); x=0;
for(;c<48|c>57;c=getchar());
for(;c>47&c<58;c=getchar()) x=x*10+(c^48);
}
O3 bool cmpx(city a,city b) {return a.x<b.x;}
O3 bool cmpz(int x,int y) {return a[x].z<a[y].z;}
O3 int main()
{
read(n),read(m);
fo(i,1,n) read(a[a[i].i=i].x),read(a[i].y),read(a[i].z),Z[i]=a[i].z;
sort(Z+1,Z+n+1), n1=unique(Z+1,Z+n+1)-Z-1;
sort(a+1,a+n+1,cmpx);
fo(i,1,n) pos[a[i].i]=i, a[i].z=lower_bound(Z+1,Z+n1+1,a[i].z)-Z;
while(m--)
{
char c=getchar();
while(c!='Q'&c!='S') c=getchar();
read(x),read(y);
if(c=='Q')
{
read(x1),read(y1),read(k);
if(x>x1) swap(x,x1);
if(y>y1) swap(y,y1);
l=1,r=n;
while(l<r)
{
mid=l+r>>1;
a[mid].x>=x?r=mid:l=mid+1;
}
if(n-l+1<k) NO
fo(i,0,n1) cnt[i]=0;
fo(i,l,n)
{
if(a[i].x>x1) break;
if(y<=a[i].y&a[i].y<=y1) cnt[0]++,cnt[a[i].z]++;
}
if(cnt[0]<k) NO
fo(i,1,n1)
{
if(cnt[i]>=k) break;
k-=cnt[i];
}
printf("%d\n",Z[i]);
}
else swap(a[pos[x+1]].z,a[pos[y+1]].z);
}
}
SolutionⅡ
- 这题实际上是李超12年出的,他当时的做法是分块+远古数据结构划分树。
- 我们可以将点按\(x\)坐标分\(\sqrt N\)块,同一块内按\(y\)坐标排序并构建划分树;这样一来,对于一个询问,它至多覆盖了\(\sqrt N\)个整块和2个散块。我们可以同时处理所有整块,就在划分树上跑;散块则暴力处理。
- 这样一来,我们就可以达到\(O(N^{1.5}+MN^{0.5}\log_2N^{0.5})\)的
高效率。 代码复杂度翻若干倍。SolutionⅢ
- 既然是求第\(K\)小,自然可以想到整体二分……
- 类比一下求区间第\(K\)小的做法:我们先离线,把所有操作放到一起进行整体二分答案,这就变成了一个一维偏序问题;那这题也可以类似地转化为整体二分+二维偏序。再打一个带修主席树!
- 时间复杂度达到了惊人的\(O((N+M)\log_2^3 N)\)!
代码复杂度翻若干倍。T2
Description
- 有个好(song)玩(ming)的游戏,有\(N(≤10^8)\)个关卡,初始有\(Q(≤5)\)条命。
- 每通过一个关卡,会得到\(u\)分和1条命,生命上限为\(Q\)。其中\(u=min(\)最近一次连续通过的关数\(,R)\)。
设通过一关的概率为\(p(\in[0,1])\),求最小的\(p\)使得期望得分超过\(S\)。
Solution
- 我原本以为如果\(p\)很小,算到后面会爆精度,需要什么奇技淫巧……
结果不需要考虑这些问题。
- 首先,我们可以预处理出所有可能的状态(当前的\(HP\)与当前的\(u\))。极限数据时也就36种。
- 可以想到二分答案+\(DP\)判可行:设\(f[i][s]\)表示当前过了第\(i\)关,状态为\(s\)的概率。这样的话,我们要一边转移一边更新答案,所以可以把答案设为状态0加入转移。
- 但是\(N\)太大,所以矩乘优化。
设二分次数为\(L\),状态数为\(S\),则时间复杂度:\(O(LS^3\log_2N)\)。
Code
#include <cstdio>
#include <cstring>
#define min(x,y) (x<y?x:y)
#define S(a) memset(a,0,sizeof a);
#define C(a,b) memcpy(a,b,sizeof a);
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef double db;
const int S=37;
int i,j,k,x,y,n,R,Q,s,rain,g[S][S],l,r,mid,tmp;
db p,t[S][S],t1[S][S],a[S],a1[S];
void work()
{
S(t) S(a)
t[0][0]=a[g[Q][0]]=1;
fo(i,1,Q)
fo(j,0,R)
if(x=g[i][j])
{
t[0][x]=p*min(j+1,R);
if(y=g[min(i+1,Q)][min(j+1,R)]) t[y][x]=p;
t[g[i-1][0]][x]=1-p;
}
for(tmp=n; tmp; tmp>>=1)
{
if(tmp&1)
{
C(a1,a)
fo(i,0,rain)
{
a[i]=0;
fo(j,0,rain) a[i]+=t[i][j]*a1[j];
}
}
C(t1,t)
fo(i,0,rain) fo(j,0,rain)
{
t[i][j]=0;
fo(k,0,rain) t[i][j]+=t1[i][k]*t1[k][j];
}
}
}
int main()
{
scanf("%d%d%d%d",&n,&R,&Q,&s);
fo(i,0,Q)
fo(j,0,R)
if(i>=j||i==Q) g[i][j]=++rain;
l=0, r=1e7;
while(l<r)
{
mid=l+r>>1;
p=mid/1e7;
work();
a[0]>s ? r=mid : l=mid+1;
}
p=l/1e7;
work();
a[0]>s ? printf("%.6lf",p) : puts("Impossible.");
}
T3
Description
给定二维平面上\(N(≤10^5)\)个点\((X_i,Y_i)(\in[0,10^8])\),选3个点,求它们两两之间的曼哈顿距离和的最大值和最小值。
Solution
- 3个点的曼哈顿距离和实际上是\(2*(X_{max}-X_{min}+Y_{max}-Y_{min})\),即3点的外接矩形的边长。那么1个点可以确定其中1~2项。
- 最大值其实很好求。因为对于一个点而言,它可以确定的总共有8种。我们求出这8种的最大值,配对一下即可。而由于是找最大值,所以在\(N>3\)的时候,一定不会算重复点。
难点就在求最小值了。
- 我们可以将其划分为两种情况:情况Ⅰ是1个点确定了2项,剩下2个点各确定1项;情况Ⅱ是2个点确定2项,剩下1个点什么也没确定。
- 情况Ⅰ又可细分为4种小情况,这里只考虑1个点确定右上角(即该点为\((X_{max},Y_{max})\))的情况(剩下的可以通过旋转转化而来)。那么还有剩下2个点分别是\((X_{min},Y)\)、\((Y,X_{min})\)。
- 我们按\(x\)坐标对点排序,并以\(x\)轴建立线段树,维护三个值:\(X_{min},Y_{min},min(X_{min}+Y_{min})\)。扫描线,当扫到一个点\((X_i,Y_i)\)时,先查询它的答案,再用\(Y_i\)区间修改,最后用\(X_i\)单点修改。注意区间修改时,如果某区间没有点则不修改;并且只有区间修改时才更新答案。这样就不会自己影响到自己。
- 情况Ⅱ也可细分为2种小情况,也是可以旋转转化。这个比较简单,在此不讲了。
总时间复杂度:\(O(N\log_2N)\)。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#define A v*2
#define B A+1
#define L A,l,m
#define R B,m+1,r
#define MAX(x,y) if(x<y)x=y
#define fo(i,a,b) for(i=a;i<=b;i++)
#define MS(a,x) memset(a,x,sizeof a);
using namespace std;
const int N=11e4,S=N<<3,inf=0x33333333;
int i,n,x,y,nX,X[N],nY,Y[N],a1,a2,a3,a4,a5,a6,a7,a8,tx[S],ty[S],tag[S],t[S],mt[S];
bool tv[S];
struct rain{int x,y;}a[N];
inline int min(const int&x,const int&y) {return x<y?x:y;}
inline int max(const int&x,const int&y) {return x>y?x:y;}
inline bool cmp(const rain&a,const rain&b) {return a.x<b.x|a.x==b.x&a.y<b.y;}
inline void MIN(int&a,const int&b) {if(a>b)a=b;}
inline void New(const int&v) {if(!tv[v]) tv[v]=1;}
inline void update(const int&v)
{
if( tv[A]&!tv[B]) tx[v]=tx[A], ty[v]=ty[A], t[v]=t[A], mt[v]=mt[A];
else
if(!tv[A]& tv[B]) tx[v]=tx[B], ty[v]=ty[B], t[v]=t[B], mt[v]=mt[B];
else
tx[v]=min(tx[A],tx[B]), ty[v]=min(ty[A],ty[B]), t[v]=min(t[A],t[B]), mt[v]=max(mt[A],mt[B]);
}
inline void mask(const int&v,const int&y)
{
if(mt[v]>tx[v]+y) mt[v]=tx[v]+(ty[v]=tag[v]=y), MIN(t[v],mt[v]);
}
inline void push(const int&v)
{
if(tag[v]==inf) return;
New(A), mask(A,tag[v]);
New(B), mask(B,tag[v]);
tag[v]=inf;
}
int que(int v,int l,int r)
{
if(!tv[v]|tx[v]==inf|ty[v]==inf|a[i].y<l) return inf;
if(a[i].y>=r) return t[v];
push(v);
int m=l+r>>1;
return min(que(L),que(R));
}
void mdf(int v,int l,int r)
{
if(!tv[v]|a[i].y>r) return;
if(a[i].y<=l) {mask(v,-y); return;}
push(v);
int m=l+r>>1;
mdf(L), mdf(R);
update(v);
}
void add(int v,int l,int r)
{
New(v);
if(l==r) {tx[v]=-x; return;}
push(v);
int m=l+r>>1;
a[i].y<=m ? add(L) : add(R);
update(v);
}
void work1()
{
sort(a+1,a+n+1,cmp);
MS(tv,0) MS(tx,51) MS(ty,51) MS(tag,51) MS(t,51) MS(mt,50)
fo(i,1,n)
{
x=X[a[i].x], y=Y[a[i].y];
MIN(a1,2*(que(1,1,nY)+x+y));
mdf(1,1,nY);
add(1,1,nY);
}
}
int Que(int v,int l,int r,bool k)
{
if(!tv[v]|a[i].y<l) return inf;
if(a[i].y>=r) return k?t[v]:tx[v];
int m=l+r>>1;
return min(Que(L,k),Que(R,k));
}
void Mdf(int v,int l,int r,int pv,bool k)
{
New(v);
if(l==r) {k?MIN(t[v],pv):MIN(tx[v],pv); return;}
int m=l+r>>1;
a[i].y<=m ? Mdf(L,pv,k) : Mdf(R,pv,k);
tx[v]=min(tx[A],tx[B]), t[v]=min(t[A],t[B]);
}
void work2()
{
MS(tv,0) MS(tx,51) MS(t,51)
fo(i,1,n)
{
x=X[a[i].x], y=Y[a[i].y];
MIN(a1,2*(Que(1,1,nY,1)+x+y));
Mdf(1,1,nY,Que(1,1,nY,0),1);
Mdf(1,1,nY,-x-y,0);
}
}
void rotate()
{
fo(i,1,n) a[i]={-Y[a[i].y],X[a[i].x]};
fo(i,1,n) X[i]=a[i].x, Y[i]=a[i].y;
sort(X+1,X+n+1), nX=unique(X+1,X+n+1)-X-1;
sort(Y+1,Y+n+1), nY=unique(Y+1,Y+n+1)-Y-1;
fo(i,1,n) a[i]={lower_bound(X+1,X+nX+1,a[i].x)-X,lower_bound(Y+1,Y+nY+1,a[i].y)-Y};
}
int main()
{
scanf("%d",&n);
a1=a2=a3=a4=a5=a6=a7=a8=-inf;
fo(i,1,n)
{
scanf("%d%d",&X[i],&Y[i]), a[i]={X[i],Y[i]};
MAX(a1, X[i]+Y[i]);
MAX(a2,-X[i]+Y[i]);
MAX(a3,-X[i]-Y[i]);
MAX(a4, X[i]-Y[i]);
MAX(a5,-X[i]); MAX(a6,X[i]);
MAX(a7,-Y[i]); MAX(a8,Y[i]);
}
a1=2*max(max(a1+a3,a2+a4),max(max(a1+a5+a7,a2+a6+a7),max(a3+a6+a8,a4+a5+a8)));
printf("%d\n",a1);
a1=inf;
sort(X+1,X+n+1), nX=unique(X+1,X+n+1)-X-1;
sort(Y+1,Y+n+1), nY=unique(Y+1,Y+n+1)-Y-1;
fo(i,1,n) a[i]={lower_bound(X+1,X+nX+1,a[i].x)-X,lower_bound(Y+1,Y+nY+1,a[i].y)-Y};
work1(), work2(), rotate();
work1(), work2(), rotate();
work1(), rotate(), work1();
printf("%d",a1);
}