本文移植自个人的原博客(原文

计算几何

题目描述

花花对计算几何有着浓厚的兴趣。他经常对着平面直角坐标系发呆,思考一些有趣的问题。今天,他想到了一个十分有意思的题目:

首先,花花会在$x$轴正半轴和$y$轴正半轴分别挑选$n$个点。随后,他将$x$轴的点与$y$轴的点一一连接,形成$n$条线段,并保证任意两条线段不相交。花花确定这种连接方式有且仅有一种。最后,花花会给出$m$个询问。对于每个询问,将会给定一个点$P(x_p,y_p)$,问线段$OP$($O$ 为坐标原点)与$n$条线段会产生多少个交点?

输入第 $1$ 行包含一个正整数 $n$,表示线段的数量;

第 $2$ 行包含 $n$ 个正整数,表示花花在 $x$ 轴选取的点的横坐标;

第 $3$ 行包含 $n$ 个正整数,表示花花在 $y$ 轴选取的点的纵坐标;

第 $4$ 行包含一个正整数 $m$,表示询问数量;

随后 $m$ 行,每行包含两个正整数 $x_p$ 和 $y_p$ ,表示询问中给定的点的横、纵坐标。

样例输入

3
4 5 3
3 5 4
2
1 1
3 3

样例输出

0
3

数据范围

对于 $40 \ percent $ 的数据:$n,m ≤ 10$

另有 $20 \ percent $ 的数据:$n,m ≤ 100$

另有 $20 \ percent $ 的数据:$n,m ≤ 1000$

对于 $100 \ percent $ 的数据:$n,m ≤ 10^5,y < 2^{31}$

题解

本题考察了计算几何中较为基础的知识,根据题意,可以先将数据升序排序,然后进行二分答案。对于判定直线是否相交则可以用求零点的方法,已知每条线段的纵坐标都是大于零的,所以只需要比较当 $x=x_p$ 时的 $y_p$ 与 $y_mid$ 大小即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N =1e5+1;
int n,m,x[N],y[N];
double a[N];
typedef double db;
inline void get(int &x)
{
x=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
}
int main()
{
// freopen("geometry.in","r",stdin);
// freopen("geometry.out","w",stdout);
get(n);
for(int i=1;i<=n;++i)get(x[i]);sort(x+1,x+n+1);
for(int i=1;i<=n;++i)get(y[i]);sort(y+1,y+n+1);

get(m);
for(int i=1,xx,yy;i<=m;++i)
{
get(xx);get(yy);
int l=0,r=n,mid;
while(l!=r)
{
mid=l+r+1>>1;
if(!a[mid])a[mid]=-((db)(y[mid]))/((db)(x[mid]));
double yt=a[mid]*(db)xx+y[mid];
if(yt<=(db)yy) l=mid;
else r=mid-1;
}
printf("%d\n",l);
}
}

花花的聚会

题目描述

花花住在 H 国。H 国有 $n$ 个城市,其中 $1$ 号城市为其首都。城市间有$n − 1$条单向道路。从任意一个城市出发,都可以沿着这些单向道路一路走到首都。事实上,从任何一个城市走到首都的路径是唯一的。

过路并不是免费的。想要通过某一条道路,你必须使用一次过路券。H 国一共有 $m$ 种过路券,每张过路券以三个整数表示:$v,k,w$:你可以在城市 $v$ 以价格 $w$ 买到一张过路券。这张券可以使用 $k$ 次。这意味着,拿着这张券通过了 $k$ 条道路之后,这张券就不能再使用了。

请注意你同一时间最多只能拥有最多一张过路券。但你可以随时撕掉手中已有的过路券,并且在所在的城市再买一张。

花花家在首都。他有 $q$ 位朋友,他希望把这些朋友们都邀请到他家做客。所以他想要知道每位朋友要花多少路费。他的朋友们都很聪明,永远都会选择一条花费最少的方式到达首都。

花花需要准备晚餐去了,所以他没有时间亲自计算出朋友们将要花费的路费。你可以帮帮他么?

输入的第一行包含两个空格隔开的整数 $n$ 和 $m$,表示 H 国的城市数量和过路券的种数。

之后的 $n − 1$ 行各自包含两个数 $a_i$ 和 $b_i$ ,代表城市 $a_i$ 到城市 $b_i$ 间有一条单向道路。

之后的 $m$ 行每行包括三个整数 $v_i$ ,$k_i$ 和 $w_i$ ,表示一种过路券。

下一行包含一个整数 $q$,表示花花朋友的数量。

之后的 $q$ 行各自包含一个整数,表示花花朋友的所在城市。

输出共 $q$ 行,每一行代表一位朋友的路费。

样例输入

7 7
3 1
2 1
7 6
6 3
5 3
4 3
7 2 3
7 1 1
2 3 5
3 6 2
4 2 4
5 3 10
6 1 20
3
5
6
7

样例输出

10
22
5

数据范围

对于 $40 \ percent$ 的数据:$n,m,q ≤ 10,w_i ≤ 10$

另有 $20 \ percent$ 的数据:$n,m,q ≤ 500,w_i ≤ 100$

另有 $20 \ percent$ 的数据:$n,m,q ≤ 5000,w_i ≤ 1000$

对于 $100 \ percent$ 的数据:$n,m,q ≤ 10^5 ,w_i ≤ 10000,1 ≤ v_i,k_i ≤ n$

题解

树形动态规划。设$f[i]$为从$i$到根节点的最小花费,则可以得到状态转移方程:

$$f[y] = min {\lbrace f[x] + w[y][i] \rbrace} ( x 为 y 祖先, dep[x] - dep[y] > = k[y][i] )$$

对于寻找$min \lbrace f[x] \rbrace $的操作,则可以通过倍增或树链剖分的方法实现。(要开 $long long$ 不然会爆)

#include<iostream>
#include<algorithm>
#include<cstdio>
const int inf =0x7fffffff;
const int N =1e5+1;
const int maxlogn =21;
using namespace std;
int deep[N],head[N],head2[N];
long long st[N][maxlogn],minn[N][maxlogn],f[N];
int tot,tot2,n,m,qu;
struct edg{int t,ne;}e[N*2];
struct edg2{int k,w,ne;}e2[N*2];
void in(int x,int y)
{
e[++tot].t=y;
e[tot].ne=head[x];
head[x]=tot;
return;
}

void in2(int x,int k,int w)
{
e2[++tot2].k=k;
e2[tot2].w=w;
e2[tot2].ne=head2[x];
head2[x]=tot2;
return;
}

void getf(int x,int pre)
{
st[x][0]=pre;minn[x][0]=f[pre];
for(int i=1;i<maxlogn;i++)
{
st[x][i]=st[st[x][i-1]][i-1];
minn[x][i]=min(minn[x][i-1],minn[st[x][i-1]][i-1]);
}
f[x]=x==1?0:inf;
for(int i=head2[x];i;i=e2[i].ne)
{
long long tmp=inf,y=x,k=e2[i].k,p=0;
while(k)
{
if(k&1)tmp=min(tmp,minn[y][p]),y=st[y][p];
p++;k>>=1;
}
f[x]=min(f[x],tmp+e2[i].w);
}
for(int i=head[x];i;i=e[i].ne)
{
int y=e[i].t;
if(y!=pre)getf(y,x);
}
return;
}

int main()
{
// freopen("party.in","r",stdin);
// freopen("party.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),in(x,y),in(y,x);
for(int i=1,x,k,w;i<=m;i++)scanf("%d%d%d",&x,&k,&w),in2(x,k,w);
scanf("%d",&qu);getf(1,1);
for(int i=1,x;i<=qu;i++){scanf("%d",&x);printf("%d\n",f[x]);}
}

文本编辑器

题目描述

九发明了一个完美的文本编辑器。这个编辑器拥有两个光标(cursor),所以九能够同时在两处地方插入和删除文本。这个编辑器除了正常的编辑功能以外,还有一些只有九才知道用处的功能,例如翻转两个光标之间的文本。某一天,九把自己的完美文本编辑器给弄丢了,但是她还有好多好多文本需要处理。于是她想请聪明又智慧的你帮她实现完美文本编辑器的一些功能。

功能列表如下:

< w

w 为一个字符,“L”或“R”,表示左光标还是右光标(下同)。
该命令将选定光标向左移动,如果已经是最左端则不移动。
命令执行成功时输出“T”,若光标已经在最左端,则输出“F”。

> w

w 同上。
与< 命令不同的是,该命令将光标向右移动。
命令执行成功时输出“T”,若光标已经在最右端,则输出“F”。

I w c

w 同上。
c 是一个可见字符(33≤ ascii 码 ≤ 126),代表在该光标左侧插入该字符。
该命令始终输出“T”。

D w

w 同上。
代表删除该光标右侧的一个字符。
命令执行成功时输出“T”,若光标右侧没有字符输出“F”。

R

代表翻转左光标和右光标之间的字符。
该命令只有左光标在右光标左侧时才能执行。
(两光标重合时也不能执行)
命令执行成功时输出“T”,否则输“F”。

S

代表显示当前处理的文本。
该命令只输出文本,不输出“T”和“F”。

开始时文本编辑器中有一定内容,左光标在第一个字符左,右光标在最后一个字符右。

注意:在插入和删除操作中,没有被操作的光标与文本的相对左右位置保持不变。特别地,若两个光标重叠,操作后也仍然重叠。

输入第一行是初始时文本编辑器内容。

第二行是一个正整数 $N$,$N$ 表示操作次数。

接下来有 $N$ 行,每行有一个命令,命令格式如上方表格。

对于每个命令,按上方表格要求执行并输出。

样例输入

goodykc
11
I R u
I R l
> L
> L
> L
> L
R
D R
< R
D R
S

样例输出

T
T
T
T
T
T
T
F
T
T
goodluck

数据范围

对于 $40 \ percent$ 的数据:$1 ≤ N$ , 初始文本长度 $≤ 100$,数据不包含翻转(Reverse)操作;

另有 $30 \ percent$ 的数据:$1 ≤ N$ , 初始文本长度 $ ≤ 10^5$ ,数据不包含翻转(Reverse)操作;

另有 $20 \ percent$ 的数据:$1 ≤ N$ , 初始文本长度 $ ≤ 10^5$ ,数据包含翻转(Reverse)操作;

对于 $100 \ percent$ 的数据:$1 ≤ N$ , 初始文本长度 $ ≤ 4 × 10^6$ ,输出文件大小 $≤ 20MB$。

题解

用链表可以模拟所有操作,对于时间复杂度较高的翻转操作,可以考虑将所有的节点前驱后驱交换一下,边界的前后驱特殊处理。但这样时间复杂度仍过高,可以考虑借鉴一下树形数据结构的LAZY_TAG思想,只有在访问到该节点时才修改并交换先后驱。

具体的实现代码十分清楚,但还有许多细节要注意,因为 $l,r$ 的相对位置可能互换,所以只能写左开右开或左闭右闭区间。

#include<iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=1e7+1;

int tot,m,pos[2],cnt[2],pre[N],next[N];

char opreator,ch[N],str[N];

int move(){getchar();return getchar()=='L'?0:1;}

void in(int opreator,char c)
{
++tot;
ch[tot]=c;
int u=pos[opreator],v=next[u];

pre[tot]=u;next[tot]=v;
next[u]=tot;pre[v]=tot;

if (cnt[opreator^1]>=cnt[opreator])cnt[opreator^1]++;

pos[opreator]=tot;cnt[opreator]++;
if (pos[opreator^1]==u)pos[opreator^1]=tot;
printf("T\n");
}

void ll(int opreator)
{
if (pos[opreator]==1){printf("F\n");return;}
int u=pos[opreator],v=pre[u];

if (next[v]!=u)swap(next[v],pre[v]);

pos[opreator]=v; cnt[opreator]--;
printf("T\n");
}

void rl(int opreator)
{
if (next[pos[opreator]]==2){printf("F\n");return;}
int u=next[pos[opreator]],v=next[u];

if(pre[v]!=u)swap(next[v],pre[v]);

pos[opreator]=u;cnt[opreator]++;
printf("T\n");
}

void D(int opreator)
{
if (next[pos[opreator]]== 2){printf("F\n");return;}
int u = pos[opreator], v = next[u], w = next[v];

if (pre[w]!=v) swap(next[w], pre[w]);
if (cnt[opreator^1]>cnt[opreator])cnt[opreator^1]--;
if (pos[opreator^1]==v)pos[opreator^1]=u;

next[u]= w;pre[w]=u;
printf("T\n");
}

void R()
{
if (cnt[1]-cnt[0]<=0){printf("F\n");return;}
if (cnt[1]-cnt[0]==1){printf("T\n");return;}
int now=pos[0],ne=next[now],c=pos[1],d=next[c];

swap(pre[ne], next[ne]);swap(pre[c],next[c]);
next[now]=c;pre[c]=now;next[ne]=d;pre[d]=ne;
pos[1]=ne;
printf("T\n");
}

void show()
{
int u=1;
while(true)
{
if(pre[next[u]]!= u)swap(pre[next[u]],next[next[u]]);
u=next[u];
if (u==2)break;
putchar(ch[u]);
}
printf("\n");
}

void init()
{
tot=2;
pre[1]=-1;next[1]= 2;
pre[2]=1;next[2]=-1;
pos[0]=pos[1]=cnt[0]=cnt[1]=1;
int len=strlen(str);
for(int i=0;i<len;i++)
{
++tot;
ch[tot]=str[i];
pre[tot]= i==0?1:tot-1;
next[tot]= i==len-1?2:tot+1;
}
if(len>0)
{
next[1]=3;pre[2]=tot;
pos[1]=tot;cnt[1]=len+1;
}
}

int act()
{
if(opreator=='<') ll(move());
if(opreator=='>') rl(move());
if(opreator=='I')
{
int d=move();
char c=getchar();
while (c<33||c>126)c=getchar();
in(d,c);
}
if (opreator=='D') D(move());
if (opreator=='R') R();
if (opreator=='S') show();
}
int main()
{
// freopen("editor.in","r", stdin);
// freopen("editor.out","w", stdout);
scanf("%s",str);
init();
scanf("%d",&m);
for (int i=1;i<=m;i++)
{
getchar();
opreator=getchar();
while (opreator!='<'&& opreator!='>'&&!(opreator >='A'&& opreator <='Z'))opreator=getchar();
act();
}
return 0;
}