3 计算几何
3.1 题意描述
花花对计算几何有着浓厚的兴趣。他经常对着平面直角坐标系发呆,思考一些有趣的问
题。今天,他想到了一个十分有意思的题目:
首先,花花会在x 轴正半轴和y 轴正半轴分别挑选n 个点。随后,他将x 轴的点与y 轴
的点一一连接,形成n 条线段,并保证任意两条线段不相交。花花确定这种连接方式有且仅
有一种。最后,花花会给出m 个询问。对于每个询问,将会给定一个点P(xp; yp),问线段
OP(O 为坐标原点)与n 条线段会产生多少个交点?
3.2 输入格式
第1 行包含一个正整数n,表示线段的数量;
第2 行包含n 个正整数,表示花花在x 轴选取的点的横坐标;
第3 行包含n 个正整数,表示花花在y 轴选取的点的纵坐标;
第4 行包含一个正整数m,表示询问数量;
随后m 行,每行包含两个正整数xp 和yp,表示询问中给定的点的横、纵坐标。
3.3 输出格式
共m 行,每行包含一个非负整数,表示你对这条询问给出的答案。
3.4 样例输入
3
4 5 3
3 5 4
2
1 1
3 3
3.5 样例输出
0
3
3.6 样例解释
3 条线段分别为:(3; 0) (0; 3)、(4; 0) (0; 4)、(5; 0) (0; 5)
(0; 0) (1; 1) 不与他们有交点,答案为0。
(0; 0) (3; 3) 与三条线段均有交点,答案为3。
3.7 数据规模与约定
• 对于40% 的数据:n;m <=10;
• 另有20% 的数据:n;m <=100;
• 另有20% 的数据:n;m <=1000;
• 对于100% 的数据:n;m <=10^5; 1 <= x; y <= 2^31。
4 花花的聚会
4.1 题意描述
花花住在H 国。H 国有n 个城市,其中1 号城市为其首都。城市间有n 1 条单向道
路。从任意一个城市出发,都可以沿着这些单向道路一路走到首都。事实上,从任何一个城市
走到首都的路径是唯一的。
过路并不是免费的。想要通过某一条道路,你必须使用一次过路券。H 国一共有m 种过
路券,每张过路券以三个整数表示:v k w:你可以在城市v 以价格w 买到一张过路券。这
张券可以使用k 次。这意味着,拿着这张券通过了k 条道路之后,这张券就不能再使用了。
请注意你同一时间最多只能拥有最多一张过路券。但你可以随时撕掉手中已有的过路券,
并且在所在的城市再买一张。
花花家在首都。他有q 位朋友,他希望把这些朋友们都邀请到他家做客。所以他想要知道
每位朋友要花多少路费。他的朋友们都很聪明,永远都会选择一条花费最少的方式到达首都。
花花需要准备晚餐去了,所以他没有时间亲自计算出朋友们将要花费的路费。你可以帮帮
他么?
4.2 输入格式
输入的第一行包含两个空格隔开的整数n 和m,表示H 国的城市数量和过路券的种数。
之后的n 1 行各自包含两个数ai 和bi,代表城市ai 到城市bi 间有一条单向道路。
之后的m 行每行包括三个整数vi; ki 和wi,表示一种过路券。
下一行包含一个整数q,表示花花朋友的数量。
之后的q 行各自包含一个整数,表示花花朋友的所在城市。
4.3 输出格式
输出共q 行,每一行代表一位朋友的路费。
4.4 样例输入
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
4.5 样例输出
10
22
5
4.6 样例解释
对于第一位朋友,他在5 号城市只能购买一种过路券,花费10 元并且可以使用3 次。这
足够他走到首都,因此总花费是10 元。
对于第二位朋友,他在6 号城市只能购买20 元的过路券,并且只能使用一次。之后,他
可以在3 号城市购买2 元,可以使用3 次的过路券走到首都。总花费是22 元。
对于第三位朋友,他在7 号城市可以购买两种过路券。他可以花3 元买一张可以使用2
次的券,然后在3 号城市再买一张2 元,可以使用3 次的券,走到首都。总花费是5 元,而且
其他的购买方式不会比这种更省钱。
4.7 数据规模与约定
• 对于40% 的数据:n; m; q <=10;wi <= 10;
• 另有20% 的数据:n; m; q <=500;wi <=100;
• 另有20% 的数据:n; m; q <=5000;wi <= 1000;
• 对于100% 的数据:n; m; q <=105;wi <= 10000; 1 <= vi; ki <= n。
5 文本编辑器
5.1 题意描述
九发明了一个完美的文本编辑器。这个编辑器拥有两个光标(cursor),所以九能够同时
在两处地方插入和删除文本。这个编辑器除了正常的编辑功能以外,还有一些只有九才知道用
处的功能,例如翻转两个光标之间的文本。某一天,九把自己的完美文本编辑器给弄丢了,但
是她还有好多好多文本需要处理。于是她想请聪明又智慧的你帮她实现完美文本编辑器的一
些功能。
功能列表如下:
功能名称命令格式说明
< (move left) < w
w 为一个字符,“L”或“R”,表示左光标还是右光标(下同)。
该命令将选定光标向左移动,如果已经是最左端则不移动。
命令执行成功时输出“T”,若光标已经在最左端,则输出“F”。
> (move right) > w
w 同上。
与< 命令不同的是,该命令将光标向右移动。
命令执行成功时输出“T”,若光标已经在最右端,则输出“F”。
I (insert) I w c
w 同上。
c 是一个可见字符(33 ascii 码 126),代表在该光标左侧插入该字符。
该命令始终输出“T”。
D (delete) D w
w 同上。
代表删除该光标右侧的一个字符。
命令执行成功时输出“T”,若光标右侧没有字符输出“F”。
R (reverse) R
代表翻转左光标和右光标之间的字符。
该命令只有左光标在右光标左侧时才能执行。
(两光标重合时也不能执行)
命令执行成功时输出“T”,否则输“F”。
S (show) S 代表显示当前处理的文本。
该命令只输出文本,不输出“T”和“F”。
开始时文本编辑器中有一定内容,左光标在第一个字符左,右光标在最后一个字符右。
注意:在插入和删除操作中,没有被操作的光标与文本的相对左右位置保持不变。特别
地,若两个光标重叠,操作后也仍然重叠。
5.2 输入格式
第一行是初始时文本编辑器内容。
第二行是一个正整数N,N 表示操作次数。
接下来有N 行,每行有一个命令,命令格式如上方表格。
5.3 输出格式
对于每个命令,按上方表格要求执行并输出。
5.4 样例输入
goodykc
11
I R u
I R l
> L
> L
> L
> L
R
D R
< R
D R
S
5.5 样例输出
T
T
T
T
T
T
T
F
T
T
goodluck
5.6 样例解释
[goodykc]
[goodykcu]
[goodykcul]
g[oodykcul]
go[odykcul]
goo[dykcul]
good[ykcul]
good[lucky]
good[lucky](光标右边没有字符,失败删除)
good[luck]y
good[luck]
goodluck
5.7 数据规模与约定
• 对于40% 的数据:1 <= N , 初始文本长度 100,数据不包含翻转(Reverse)操作;
• 另有30% 的数据:1 <=N , 初始文本长度 10^5,数据不包含翻转(Reverse)操作;
• 另有20% 的数据:1 <=N , 初始文本长度 10^5,数据包含翻转(Reverse)操作;
• 对于100% 的数据:1 <=N , 初始文本长度 4 * 10^6,输出文件大小 20MB;
解题报告:
第一题:要点:线性规划(数学判断);二分查找。连边的时候直接排序就可以了,然后再二分。把二分模板化。。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #define maxn 100005 6 using namespace std; 7 int n,m,x[maxn],y[maxn],ans; 8 struct pp{ 9 int xi,yi; 10 }; 11 pp linee[maxn]; 12 bool pd(int a,int b,int x) 13 { 14 double v=0,w=0; 15 v=(double)linee[x].yi/linee[x].xi*a; 16 w=(double)linee[x].yi-b; 17 if (v<w) return true; 18 else return false; 19 } 20 void doit(int a,int b) 21 { 22 int l=1,r=n; 23 while (l<r) 24 { 25 int mid=(l+r)>>1; 26 if (pd(a,b,mid)) r=mid; 27 else l=mid+1; 28 } 29 if (pd(a,b,l)) ans=l-1; 30 else ans=l; 31 return ; 32 } 33 int main() 34 { 35 freopen("geometry.in","r",stdin); 36 freopen("geometry.out","w",stdout); 37 cin>>n; 38 for (int i=1;i<=n;i++) 39 scanf("%d",&x[i]); 40 for (int i=1;i<=n;i++) 41 scanf("%d",&y[i]); 42 sort(x+1,x+1+n); 43 sort(y+1,y+1+n); 44 for (int i=1;i<=n;i++) 45 { 46 linee[i].xi=x[i]; 47 linee[i].yi=y[i]; 48 } 49 cin>>m; 50 for (int i=1;i<=m;i++) 51 { 52 int a,b;ans=0; 53 scanf("%d%d",&a,&b); 54 doit(a,b); 55 printf("%d\n",ans); 56 } 57 return 0; 58 }
第二题:知道是树规,但是,不会。好吧,本来也想建边来做,结果放弃了,后来听说建边可以过80。。我的暴力只过了40,还是该写建边的。
80%:把车票理解为从 v 城市可以到它的上面 k 个祖先城市,权值都是 w ,然后找最短路就可以了。
100%:用dfs+倍增+dp。以前的倍增都是单独一个函数做的,现在和dfs一起,一边dp一边倍增。dp:这里的树规是从上往下dp的,和以往的不一样,意为从 u 节点到 1 节点的最小花费。然后倍增往上跳。把每个城市的票数作为循环,使用次数作为第二层循环,取其中最小值的最小值+当前车票的花费wi 。
1 #include<iostream> 2 #include<cstdio> 3 #include<iostream> 4 #include<cstring> 5 #include<algorithm> 6 #define maxn 100005 7 #define inf 12345678 8 using namespace std; 9 int n,m,q; 10 int tot,he[maxn],to[maxn],ne[maxn],phe[maxn],toto; 11 int ans[maxn],depth[maxn],f[15][maxn*4],dp[15][maxn*4]; 12 struct pp{ 13 int next; 14 int ki,wi; 15 }; 16 pp city[maxn]; 17 void add(int a,int b) 18 { 19 tot++;to[tot]=b;ne[tot]=he[a];he[a]=tot; 20 } 21 void add2(int v,int k,int w) 22 { 23 city[++toto].next=phe[v]; 24 city[toto].ki=k; 25 city[toto].wi=w; 26 phe[v]=toto; 27 } 28 void dfs(int x,int fa,int deep)//dfs嵌套倍增 29 { 30 depth[x]=deep; 31 if (fa){ 32 f[0][x]=fa; 33 dp[0][x]=ans[fa]; 34 } 35 for (int i=1;i<=14;i++) 36 { 37 f[i][x]=f[i-1][f[i-1][x]]; 38 dp[i][x]=min(dp[i-1][x],dp[i-1][f[i-1][x]]); 39 } 40 if (fa) ans[x]=inf; 41 else ans[x]=0; 42 for (int i=phe[x];i;i=city[i].next) 43 { 44 pp now=city[i]; 45 int tmp=inf,cur=x; 46 for (int j=14;j>=0;j--) 47 if ((1<<j)<=now.ki&&depth[f[j][cur]]>=1){ 48 tmp=min(tmp,dp[j][cur]); 49 cur=f[j][cur]; 50 now.ki-=(1<<j); 51 } 52 ans[x]=min(ans[x],tmp+now.wi); 53 } 54 for (int i=he[x];i;i=ne[i]) 55 dfs(to[i],x,deep+1); 56 } 57 int main() 58 { 59 freopen("party.in","r",stdin); 60 freopen("party.out","w",stdout); 61 cin>>n>>m; 62 for (int i=1;i<n;i++) 63 { 64 int a,b; 65 scanf("%d%d",&a,&b); 66 add(b,a); 67 } 68 for (int i=1;i<=m;i++) 69 { 70 int v,k,w; 71 scanf("%d%d%d",&v,&k,&w); 72 add2(v,k,w); 73 } 74 dfs(1,0,1);// now ,father ,depth 75 cin>>q; 76 for (int i=1;i<=q;i++) 77 { 78 int x; 79 scanf("%d",&x); 80 printf("%d\n",ans[x]); 81 } 82 return 0; 83 }
第三题:链表。考试的时候写的暴力,结果没有过。链表没有学过,只好先从头开始。双向链表:对于每一个节点记一个前驱,一个后继。对于翻转,做延迟更新的处理,即只修改区间前后的四个节点的前驱后继,更新时只需判断此节点的前驱的后继是不是此节点,如果不是,就交换它的前驱后继,这个由画图可以得到。
1、左移:用后继的后继的前驱不等于后继判断更新,因为光标所指的节点是光标的左边节点,所以如果移动左光标,意味着此光标所指的节点的后继的后继需要判断更新;同理,对于右移而言,它所指的节点也是它左边的节点,所以它只需要判断更新此节点的前驱。
2、删除:需要判断当前节点。
3、注意各个操作的光标修改。更多的注释写在代码里了。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<string> 5 #define maxn 8000005 //not 4000005 因为还要insert 6 using namespace std; 7 int n,m,len,idx;//idx:记录字符个数 8 int sur[maxn],pre[maxn],pos[2],cnt[2]; 9 //cnt:判断左右节点的相对位置,pos:左右节点的位置,pre:前驱,sur:后继 10 char ch[maxn],in[maxn/2],c,b,add;//ch:修改后的字符,in:原字符串 11 void init() 12 { 13 scanf("%s",in);len=strlen(in); 14 pos[0]=pos[1]=cnt[0]=cnt[1]=1; 15 pre[1]=-1;sur[1]=2;//将1、2位置空出来,1节点作为字符的开头,2节点作为字符的末尾 16 pre[2]=1;sur[2]=-1;//可以理解为一个环 但开头节点无前驱,尾节点无后继 17 idx=2; 18 for (int i=0;i<len;i++) 19 { 20 ch[++idx]=in[i]; 21 pre[idx]= i==0 ? 1:idx-1; 22 sur[idx]= i==len-1 ? 2:idx+1; 23 } 24 if (len>0){ 25 pos[1]=idx;//更新右节点位置 26 cnt[1]=idx+1;//更新右节点相对位置 27 sur[1]=3; 28 pre[2]=idx;//更新尾节点的前驱 29 } 30 cin>>n; 31 } 32 int pd() 33 { 34 getchar();b=getchar(); 35 return b=='L'? 0:1; 36 } 37 void move_left(int op) 38 { 39 if (pos[op]==1) putchar('F');//putchar 更快 注意是 ‘ ’ 40 else { 41 int u=pos[op],v=pre[u]; 42 if (u!=sur[v]) swap(sur[v],pre[v]); 43 pos[op]=v; 44 cnt[op]--; 45 putchar('T'); 46 } 47 putchar('\n'); 48 } 49 void move_right(int op) 50 { 51 if(sur[pos[op]]==2) putchar('F'); 52 else { 53 int u=sur[pos[op]],v=sur[u]; 54 if(pre[v]!=u) swap(pre[v],sur[v]); 55 pos[op]=u;//not v 56 cnt[op]++; 57 putchar('T'); 58 } 59 putchar('\n'); 60 } 61 void in_sert(int op) 62 { 63 getchar();add=getchar(); 64 int u=pos[op],v=sur[u]; 65 ch[++idx]=add; 66 sur[idx]=v; 67 pre[idx]=u; 68 sur[u]=idx; 69 pre[v]=idx; 70 pos[op]=idx; 71 if (pos[op^1]==u) pos[op^1]=idx;//重合移动 (op^1 即相反节点1->0,0->1) 72 if (cnt[op^1]>=cnt[op]) cnt[op^1]++;//如果另一个光标在op后面,那它的cnt也要改 73 putchar('T'); 74 putchar('\n'); 75 } 76 void de_lete(int op) 77 { 78 if (sur[pos[op]]==2) putchar('F'); 79 else { 80 int u=sur[pos[op]],v=sur[u]; 81 if (pre[v]!=u) swap(pre[v],sur[v]); 82 sur[pos[op]]=v; 83 pre[v]=pos[op]; 84 if (pos[op^1]==u) pos[op^1]=v;//*** 85 if (cnt[op^1]>cnt[op]) cnt[op^1]--;//*** 86 putchar('T'); 87 } 88 putchar('\n'); 89 } 90 void re_verase() 91 { 92 if (cnt[1]-cnt[0]<=0) putchar('F');//L 在 R 右边或重合 93 else { 94 if (cnt[1]-cnt[0]>1){ 95 int a=pos[0],b=sur[a],c=pos[1],d=sur[c]; 96 swap(pre[b],sur[b]);swap(pre[c],sur[c]); 97 sur[a]=c;sur[b]=d; 98 pre[c]=a;pre[d]=b; 99 pos[1]=b; 100 } 101 putchar('T'); 102 } 103 putchar('\n'); 104 } 105 void shown() 106 { 107 int u=1; 108 while (true){ 109 if (pre[sur[u]]!=u) swap(pre[sur[u]],sur[sur[u]]); 110 u=sur[u]; 111 if (u==2) break; 112 putchar(ch[u]); 113 } 114 putchar('\n'); 115 } 116 void work() 117 { 118 for (int i=1;i<=n;i++) 119 { 120 getchar(); 121 c=getchar(); 122 switch(c) 123 { 124 case'<':move_left(pd());break; 125 case'>':move_right(pd());break; 126 case'I':in_sert(pd());break; 127 case'D':de_lete(pd());break; 128 case'R':re_verase();break; 129 case'S':shown();break; 130 } 131 } 132 } 133 int main() 134 { 135 freopen("editor.in","r",stdin); 136 freopen("editor.out","w",stdout); 137 init(); 138 work(); 139 return 0; 140 }