【noip模拟赛】密码

http://hzwer.com/4957.html


表示没看懂算法3难过


【问题描述】

哪里有压迫,哪里就有反抗。

moreD的宠物在法庭的帮助下终于反抗了。作为一只聪明的宠物,他打算把魔法使moreD的魔法书盗去,夺取moreD的魔法能力。但moreD怎么会让自己的魔法书轻易地被盗取?moreD在魔法书上设置了一个密码锁,密码锁上有一个问题。

施以斯卧铺魔法吧,你有M次机会,如此将得完美密码。

然后是一串小写字母串。

moreD的宠物斯卧铺魔法就是施法时的字符串其中相邻两位交换。

moreD对于完美密码的定义自然是最小字典序了。

请帮助moreD的宠物,想出密码吧。

【输入格式】

第一行一个整数M,表示操作次数。

第二行一串小写字母组成的字符串S,如题目所示。

【输出格式】

输出完美密码。

【输入样例】

3

dcba

【输出样例】

adcb

【数据范围】

对于30%的数据|S|≤10

对于60%的数据|S|≤3,000

对于100%的数据8≤|S|≤100,000 M≤(|S|-8)^2+2

【后记】

宠物最终战胜了moreD,和自己的宠物快乐地生活着。

【样例解释】

先对第3,4两位施法,字符串变成dcab,然后对第2,3两位施法,字符串变成dacb,最后对第1,2两位施法,字符串变成adcb。

题解

先说说我的逗比做法

贪心,字典序要满足前面的尽量小,从前往后依次确定每一位,若当前处理到x位

则应该从x到x+K位中选最小的交换上来

这样复杂度是n^2,考虑用数据结构优化

由于我选择了线段树,所以走上了不归路,线段树查询区间最值是基本操作,但是还要在区间内删除一个数,删除后下次确定i到i+K位就变得有些棘手了,因为并不能确定i+K位到底在什么位置,唯一我能想到的办法是使得线段树支持求区间剩下的数的个数,通过二分来寻找右边界,这样复杂度变为n(logn)^2。。。


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring>
#define inf 1000000000
#define ll long long 
using namespace std;
ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
char ch[100005];
ll K;
int n,a[100005];
struct data{int l,r,mn,val,sum;}t[400005];
void update(int k)
{
    if(t[k<<1].val<=t[k<<1|1].val)
		t[k].val=t[k<<1].val,t[k].mn=t[k<<1].mn;
    else t[k].val=t[k<<1|1].val,t[k].mn=t[k<<1|1].mn;
    t[k].sum=t[k<<1].sum+t[k<<1|1].sum;
}
void build(int k,int l,int r)
{
    t[k].l=l;t[k].r=r;
    if(l==r){t[k].val=inf;t[k].mn=l;return;}
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    update(k);
}
void modify(int k,int pos,int val)
{
    int l=t[k].l,r=t[k].r;
    if(l==r){t[k].val=val;if(val!=inf)t[k].sum=1;else t[k].sum=0;return;}
    int mid=(l+r)>>1;
    if(pos<=mid)
		modify(k<<1,pos,val);
    else modify(k<<1|1,pos,val);
    update(k);
}
int query(int k,int x,int y)
{
    int l=t[k].l,r=t[k].r;
    if(x==l&&y==r)return k;
    int mid=(l+r)>>1;
    if(y<=mid)return query(k<<1,x,y);
    else if(x>mid)return query(k<<1|1,x,y);
    else 
    {
		int t1=query(k<<1,x,mid),t2=query(k<<1|1,mid+1,y);
		if(t[t1].val<=t[t2].val)return t1;
		else return t2;
    }
}
int getsum(int k,int x,int y)
{
    int l=t[k].l,r=t[k].r;
    if(x==l&&y==r)return t[k].sum;
    int mid=(l+r)>>1;
    if(y<=mid)return getsum(k<<1,x,y);
    else if(x>mid)return getsum(k<<1|1,x,y);
    else return getsum(k<<1,x,mid)+getsum(k<<1|1,mid+1,y);
}
int find(int x,ll val)
{
    int l=x+1,r=n,tmp=x;
    while(l<=r)
    {
		int mid=(l+r)>>1;
		if(getsum(1,x+1,mid)<=val)tmp=mid,l=mid+1;
		else r=mid-1;
    }
    return tmp;
}
bool solve(int x)
{
    int now=t[query(1,x,x)].val;
    if(now==inf)return 0;
    if(!K){printf("%c",now+'a');return 0;}
    int lim=find(x,K);
    int tmp=query(1,x+1,lim);
    if(t[tmp].val>=now){printf("%c",now+'a');return 0;}
    printf("%c",t[tmp].val+'a');
    K-=getsum(1,x,t[tmp].mn)-1;
    modify(1,t[tmp].mn,inf);
    return 1;
}
int main()
{
    K=read();
    scanf("%s",ch+1);
    n=strlen(ch+1);
    for(int i=1;i<=n;i++)a[i]=ch[i]-'a';
    build(1,1,n);
    for(int i=1;i<=n;i++)
		modify(1,i,a[i]);
    for(int i=1;i<n;)
		if(!solve(i))i++;
    int tmp=t[query(1,n,n)].val;
    if(tmp!=inf)printf("%c",tmp+'a');
    puts("");
    return 0;
}

如果这样想似乎用splay应该是正确选择?

顺便贴下正解

【题目模型】

给出初始字符串和操作数,要求在操作数范围内仅使用每次交换其中相邻两位的操作使得字符串字典序尽量小。

【算法一】

深度优先搜索:每次枚举交换的位置,时间复杂度O(N^M)。期望得分30%

【算法二】

对于此类问题可以使用贪心思想:由于题目要求字典序,可以贪心地从前到后确定每一位的字母。字母肯定是从小到大地枚举,操作距离内的枚举字母的话肯定会贪心地把这个字母换入目标位置。而选择枚举的字母就是贪心地选择尽量前的字母。证明略。

时间复杂度为O(N^2),空间复杂度为O(N)。期望得分60%

【算法三】

算法二可以进行优化。

算法主要耗时在寻找每个字母目前最前是哪一个,可以用链表维护。另外目前需要的操作数可以就等于目标字母所在位置之前有多少个剩余的没有用的字母,可以用树状数组维护。

时间复杂度为O(NlogN),空间复杂度为O(N)。期望得分100%

【惊喜】

无视操作数可以骗50%哦,再结合暴力有70%.结合算法二有80%。大家要相信数据是很水的。比赛时有题目不会做,也没有时间想,不妨考虑骗分吧。仅供参考。






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值