NOIP机房模拟 【计算几何】【花花的聚会】【文本编辑器】

七月最后一个星期的集训,周一一来就模拟考试。。

计算几何

题意描述

花花对计算几何有着浓厚的兴趣。他经常对着平面直角坐标系发呆,思考一些有趣的问题。今天,他想到了一个十分有意思的题目:
首先,花花会在x 轴正半轴和y 轴正半轴分别挑选n 个点。随后,他将x 轴的点与y 轴的点一一连接,形成n 条线段,并保证任意两条线段不相交。花花确定这种连接方式有且仅有一种。最后,花花会给出m 个询问。对于每个询问,将会给定一个点P(xp; yp),问线段
OP(O 为坐标原点)与n 条线段会产生多少个交点?

输入格式

第1 行包含一个正整数n,表示线段的数量;
第2 行包含n 个正整数,表示花花在x 轴选取的点的横坐标;
第3 行包含n 个正整数,表示花花在y 轴选取的点的纵坐标;
第4 行包含一个正整数m,表示询问数量;
随后m 行,每行包含两个正整数xp 和yp,表示询问中给定的点的横、纵坐标。

输出格式

共m 行,每行包含一个非负整数,表示你对这条询问给出的答案。

样例输入

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

样例输出

0
3

思路:

考虑二分。
对于每一个询问,OP只和在P左下角的线段相交。故我们可以二分
在P左下角的最靠上的线段,从而得到答案。

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=1e5;
int n,m;
long long one,two;
long long x[N+5],y[N+5];
int main()
{
    freopen("geometry.in","r",stdin);
    freopen("geometry.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    scanf("%d",&x[i]);
    for (int i=1;i<=n;i++)
    scanf("%d",&y[i]);
    sort(x+1,x+1+n);sort(y+1,y+1+n);
    scanf("%d",&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&one,&two);
        int lf=1,rt=n;
        while(lf<=rt)
        {
            int mid=(lf+rt)>>1;
            if (x[mid]*two+y[mid]*one>=x[mid]*y[mid]) lf=mid+1;
            else rt=mid-1;
        }
        printf("%d\n",rt);
    }
    return 0;
}

花花的聚会

题意描述

花花住在H 国。H 国有n 个城市,其中1 号城市为其首都。城市间有n - 1 条单向道路。从任意一个城市出发,都可以沿着这些单向道路一路走到首都。事实上,从任何一个城市走到首都的路径是唯一的。
过路并不是免费的。想要通过某一条道路,你必须使用一次过路券。H 国一共有m 种过路券,每张过路券以三个整数表示:v k w:你可以在城市v 以价格w 买到一张过路券。这张券可以使用k 次。这意味着,拿着这张券通过了 条道路之后,这张券就不能再使用了。
请注意你同一时间最多只能拥有最多一张过路券。但你可以随时撕掉手中已有的过路券,并且在所在的城市再买一张。
花花家在首都。他有q 位朋友,他希望把这些朋友们都邀请到他家做客。所以他想要知道每位朋友要花多少路费。他的朋友们都很聪明,永远都会选择一条花费最少的方式到达首都。
花花需要准备晚餐去了,所以他没有时间亲自计算出朋友们将要花费的路费。你可以帮帮他么?

输入格式

输入的第一行包含两个空格隔开的整数n 和m,表示H 国的城市数量和过路券的种数。
之后的n - 1 行各自包含两个数ai 和bi,代表城市ai 到城市bi 间有一条单向道路。
之后的m 行每行包括三个整数vi; ki 和wi,表示一种过路券。
下一行包含一个整数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

思路

不难发现,我们可以用动态规划的思想来解决这个问题。记f[u] 为u 跳到根的最少代价。则不难得出转移方程:f[u] = minff[v]+wig,其中v 是u 的祖先,且存在一种车票ui;wi; ki,并满足dep[u] -dep[v]<= ki。这个DP方程的边界条件为f[root] = 0。这样对于每种车票,我们可以暴力枚举所有合法的v,从而求得答案。
但是,考虑优化解法三的DP转移。事实上我们只需在较快的时间内求得树上某一段区间的min,即可较好地解决这个问题。而维护树上区间最值有树链剖分、动态树等许多数据结构能够胜任。在这里我们考虑用倍增的思想,维护u 往上跳2i 步到的祖先v 和u 到v 这一段f 值的最小值,这样我们对于每一个节点u 可以在O(logn) 的时间内求得minff[v]g。
时间复杂度为O(nlogn),期望得分100 分。

代码

然而我用记忆化搜索照样过,速度飞快。。。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=1e5;
const int INF=1e9;
int head[N+5],num,father[N+5],n,m,q,dp[N+5];
struct edge
{
    int k,w,next;
};
edge ed[N+5];
void build(int v,int k,int w)
{
    ed[++num].k=k;
    ed[num].w=w;
    ed[num].next=head[v];
    head[v]=num;
}
int work(int u)
{
    if (u==1) return 0;
    if (dp[u]!=-1) return dp[u];
    dp[u]=INF;
    for (int i=head[u];i!=-1;i=ed[i].next)
    {
        int tot=1,now=father[u],k=ed[i].k;
        while(now>=1&&tot<=k)
        {
            dp[u]=min(dp[u],work(now)+ed[i].w);
            now=father[now];
            tot++;
        }
    }
    return dp[u];
}
int main()
{
    freopen("party.in","r",stdin);
    freopen("party.out","w",stdout);
    memset(dp,-1,sizeof(dp));
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n-1;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        father[u]=v;
    }
    for (int i=1;i<=m;i++)
    {
        int v,k,w;
        scanf("%d%d%d",&v,&k,&w);
        build(v,k,w);
    }
    scanf("%d",&q);
    for (int i=1;i<=q;i++)
    {
        int u;
        scanf("%d",&u);
        printf("%d\n",work(u));
    }
    return 0;
}

文本编辑器

题意描述

九发明了一个完美的文本编辑器。这个编辑器拥有两个光标(cursor),所以九能够同时
在两处地方插入和删除文本。这个编辑器除了正常的编辑功能以外,还有一些只有九才知道用
处的功能,例如翻转两个光标之间的文本。某一天,九把自己的完美文本编辑器给弄丢了,但
是她还有好多好多文本需要处理。于是她想请聪明又智慧的你帮她实现完美文本编辑器的一些功能。
功能列表如下:
这里写图片描述
注意:在插入和删除操作中,没有被操作的光标与文本的相对左右位置保持不变。特别地,若两个光标重叠,操作后也仍然重叠。

输入格式

第一行是初始时文本编辑器内容。
第二行是一个正整数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

样例解释

[goodykc]
[goodykcu]
[goodykcul]
g[oodykcul]
go[odykcul]
goo[dykcul]
good[ykcul]
good[lucky]
good[lucky](光标右边没有字符,失败删除)
good[luck]y
good[luck]
goodluck

思路

对于n = 4*106 的数据,使用平衡树复杂度太高。我们继续考虑使用链表来维护文本。
我们考虑在进行翻转操作时,只有两个端点元素的前驱和后继指针发生了实际变化,而中间元素的前驱和后继指针只是发生了交换。我们可以借鉴Splay的lazy标记的思想,对于每一个翻转操作,我们只实时修改两端的元素,对于中间元素只在两端维护标记。只有当中间元素被访问到时,才交换其前驱和后继指针,并移动标记。对于标记的消除和多个标记的情况,需要进行相关讨论。
事实上使用标记来处理中间元素的指针交换需讨论较多的情况,较为复杂。事实上我们可以不必维护标记。当我们发现左边下下个元素的后继指针不是左边下个元素时我们便可以交换左边下下个元素的前驱和后继指针;类似的,当我们发现右边下下个元素的前驱指针不是右边下个元素时我们便可以交换右边下下个元素的前驱和后继指针。这样我们就可以使用链表,较轻松地实现区间翻转操作了。
时间复杂度为O(n),期望得分100 分。
4

代码

暴力!

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
const int N=100000+5;
char s[N],type,kind;
int len,n,lf,rt;//第x个的左边 
void Insert()
{
    char now[N],A,B;
    scanf(" %c %c",&A,&B);
    if (A=='R')
    {
        strcpy(now,s+rt);
        strcpy(s+rt+1,now);
        s[rt]=B;
        len++;rt++;
    }
    else if (A=='L')
    {
        strcpy(now,s+lf);
        strcpy(s+rt+1,now);
        s[lf]=B;
        len++;rt++;
    }
    printf("T\n");
}
void moveleft()
{
    char A;
    scanf(" %c",&A);
    if (A=='L') 
    {
        if (lf<=1) printf("F\n");
        else {lf--;printf("T\n");}
    }
    else if (A=='R')
    {
        if (rt<=1) printf("F\n");
        else {rt--;printf("T\n");}
    }
}
void moveright()
{
    char A;
    scanf(" %c",&A);
    if (A=='L') 
    {
        if (lf>=len+1) printf("F\n");
        else {lf++;printf("T\n");}
    }
    else if (A=='R')
    {
        if (rt>=len+1) printf("F\n");
        else {rt++;printf("T\n");}
    }
}
void del()
{
    char A,now[N];
    scanf(" %c",&A);
    if (A=='L')
    {
        if (lf==len+1) {printf("F\n");return ;}
        strcpy(now,s+lf+1);
        strcpy(s+lf,now);
        printf("T\n");
    }
    else if (A=='R')
    {
        if (rt==len+1) {printf("F\n");return ;}
        strcpy(now,s+rt+1);
        strcpy(s+rt,now);
        printf("T\n");
    }
}
void show()
{
    for (int i=1;i<=len;i++)
    printf("%c",s[i]);
    printf("\n");
}
void reverse()
{
    char now[N];
    if (lf>=rt) {printf("F\n");return ;}
    else
    {
        int L=lf,R=rt-1;
        if ((rt-lf)%2==1) 
        {
            int mid=lf+(rt-lf-1)/2;char t;
            for (int i;;i++)
            {
                if (L>=mid) break;
                t=s[L];s[L]=s[R];s[R]=t;
                L++;R--;
            }
        } 
        else
        {
            int mid=lf+(rt-lf-1)/2;char t;
            for (int i;;i++)
            {
                if (L>mid) break;
                t=s[L];s[L]=s[R];s[R]=t;
                L++;R--;
            }
        }
        printf("T\n");
    }
}
int main()
{
    freopen("editor.in","r",stdin);
    freopen("editor.out","w",stdout);
    scanf("%s",s+1);
    len=strlen(s+1);
    lf=1;rt=len+1;
    scanf("%d",&n);
    while(n--)
    {
        scanf(" %c",&type);
        if (type=='I') Insert();
        else if (type=='<') moveleft();
        else if (type=='>') moveright();    
        else if (type=='D') del();
        else if (type=='R') reverse();
        else if (type=='S') show();
    }
    return 0;
}

正解

#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
using namespace std;
const int N=10000000;
int pre[N],suf[N];
char ch[N],st[N];
int idx,pos[2],cnt[2];
void swap(int &a,int &b) 
{
    int c=a;
    a=b;
    b=c;
}
void Insert(int opt, char c) 
{
    ++idx;
    ch[idx]=c;
    int u=pos[opt],v=suf[u];
    pre[idx]=u;suf[idx]=v;
    suf[u]=idx;pre[v]=idx;
    if (cnt[opt^1]>=cnt[opt]) cnt[opt^1]++;
    pos[opt]=idx;cnt[opt]++;
    if (pos[opt^1]==u) pos[opt^1]=idx;
    puts("T");
}
void LMove(int opt) 
{
    if (pos[opt] == 1) {puts("F");return ;}
    int u=pos[opt],v=pre[u];
    if (suf[v]!=u) swap(suf[v],pre[v]);
    pos[opt]=v;cnt[opt]--;
    puts("T");
} 
void RMove(int opt) 
{
    if (suf[pos[opt]]==2) {puts("F");return ;}
    int u=suf[pos[opt]], v=suf[u];
    if (pre[v]!=u) swap(suf[v],pre[v]);
    pos[opt]=u;cnt[opt]++;
    puts("T");  
}
void Delete(int opt) 
{
    if (suf[pos[opt]]==2) {puts("F");return ;}
    int u=pos[opt],v=suf[u],w=suf[v];
    if (pre[w]!=v) swap(suf[w],pre[w]);
    if (cnt[opt^1]>cnt[opt]) cnt[opt^1]--;
    if (pos[opt^1]==v) pos[opt^1]=u;
    suf[u]=w; pre[w]=u;
    puts("T");
}
void Reverse() 
{
    if (cnt[1]-cnt[0]<=0) {puts("F");return ;}
    if (cnt[1]-cnt[0]==1) {puts("T");return ;}
    int a=pos[0],b=suf[a], c=pos[1],d=suf[c];
    swap(pre[b],suf[b]); swap(pre[c],suf[c]);
    suf[a]=c;pre[c]=a; suf[b]=d; pre[d]=b;
    pos[1]=b;
    puts("T");
}
void Show() 
{
    int u=1;
    while (true) 
    {
        if (pre[suf[u]]!=u) swap(pre[suf[u]],suf[suf[u]]);
        u=suf[u];
        if (u==2) break;
        putchar(ch[u]);
    }
    putchar('\n');
}
void Build() 
{
    scanf("%s",st);
    idx=2;
    pre[1]=-1;suf[1]=2;
    pre[2]=1;suf[2]=-1;
    pos[0]=pos[1]=cnt[0]=cnt[1]=1;
    int len=strlen(st);
    for (int i=0;i<len;i++) 
    {
        ++idx;
        ch[idx]=st[i];
        pre[idx]=i==0?1:idx-1;
        suf[idx]=i==len-1?2:idx+1;
    }
    if (len>0) 
    {
        suf[1]=3;pre[2]=idx; 
        pos[1]=idx;cnt[1]=len+1;
    } 
}
int Dir() 
{
    getchar();
    return getchar()=='L'?0:1;
}
int main() 
{
    freopen("editor.in", "r", stdin);
    freopen("editor.out", "w", stdout);
    Build();
    int m;
    scanf("%d",&m);
    for (int i=1;i<=m;i++) 
    {
        getchar(); 
        char opt=getchar();
        while (opt!='<'&&opt!='>'&&!(opt>='A'&&opt<='Z')) opt=getchar(); 
        if (opt=='<') LMove(Dir());
        else if (opt=='>') RMove(Dir());
        else if (opt=='I') 
        {
            int d=Dir();
            char c=' ';
            while (c<33||c>126) c=getchar();
            Insert(d,c);
        }
        else if (opt=='D') Delete(Dir());
        else if (opt=='R') Reverse();
        else if (opt=='S') Show();
    }
    return 0;
}

大佬的代码,我再理解一下

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值