首页 > 技术文章 > 4.1模拟题

TheRoadToAu 2018-04-01 17:27 原文

期望得分:0+10+100

实际得分:0+0+100,rank 2

智乃
【题目描述】
给你N个字符串,你每次可以选择其中一个字符串的一段前缀进行翻转,但
是你必须保证这个前缀的长度是偶数。你可以进行无限次这样的操作,并且如果
两个字符串变得相同的时候,你就可以把这两个字符串都删除掉,问最后最少剩
下多少个字符串?
【输入格式】
第一行一个整数T代表数据组数。
对于每组数据,第一行一个整数N代表字符串个数。
接下来N行每行一个字符串。
【输出格式】
对于每组数据,一行一个整数代表答案。
【样例输入】
2
5
esprit
god
redotopc
odcpoter
dog
14
rats
live
stressed
to
act
as
star
desserts
of
evil
cat
sa
fo
ot

【样例输出】
3
0
【样例解释】
无。
【数据范围与规定】
对于40%的数据,字符串长度不超过8。
对于100%的数据,1 ≤ T ≤ 11,字符串长度不超过50,1 ≤ N ≤ 50。

题解:我们手动模拟一下样例,容易发现一个事实:

虽然我们可以翻转无数多次,但是如果一个区间被翻转了偶数次,那么这个区间保持不变;如果一个区间被翻转了奇数次,那么这个区间就被翻转过来了。即我们只需要考虑翻转次数是奇数次还是偶数次即可。

每次操作只能翻转一个长度为偶数的前缀意味着如果字符串长度为奇数的话,最后一个字符不可以被翻转到除了第一个字符和最后一个字符的其他位置。

此外其余的字符串可以划分为2个一组,对于每一组字符串的两个字符位置可以互换,划分出的字符串位置可以互换。

假设我们现在有一个字符串abcdefgh,我们要翻转区间$[3,4]$,那么我们可以先翻转区间$[1,4]$,然后翻转区间$[1,2]$。

如果我们要把区间$[1,2]$移动到$[5,6]$,我们可以先翻转区间$[1,2]$,再翻转区间$[5,6]$,再翻转区间$[3,4]$,最后翻转区间$[1,6]$。

即我们令$a,b,c,d\in\bf{Z}$,且假设两个区间没有交集:

假设要把区间$[2a-1,2b]$翻转过来,我们可以先对区间$[1,2b]$进行一次翻转,然后再对区间$[1,2a-2]$进行一次翻转。

假设要把区间$[2a-1,2b]$移动到$[2c-1,2d]$,我们可以先对区间$[2a-1,2b]$进行一次翻转,然后对区间$[2c-1,2d]$进行一次翻转,然后再对区间$[\max(2a-1,2c-1),\min(2b-1,2d-1)]$进行一次翻转,最后再对区间$[\min(2a-1,2c-1),\max(2b,2d)]$进行一次翻转。

很明显,对于其他操作,我们可以由上述两个操作转化而来。我们就证明出了翻转后的字符串的可能情况。

另外我们还有一个发现,如果两个字符串的长度不相等,那么无论怎么翻转它们一定不可能相等。

证明:显然。

由上所述,我们可以得到一个比较合理的算法:

对于每个字符串,我们都拆分为长度为2的子字符串,如果原字符串长度为奇数,则固定单个字符所拆分出的长度为2的字符串的第2个字符为‘\0’,对于每个拆分出来的字符串,我们保证字典序较小的字符总出现在字典序较大的字符之前,然后模拟翻转区间操作,使得字典序较小的字符出现在字典序较大的字符之前,再凑出原来的字符串,把翻转后的字符串扔到一个map里面,最终答案就是map中每一个元素出现次数对2取模的值之和(明显的,如果一个字符串出现了奇数次,那么会剩下一个字符串无法被消除)。

麻耶
【问题描述】
油库里是幻想乡特有的一种生物。每只油库里都有一个战斗力值和一个能量
值。当两只油库里战斗时,总是战斗力值高的一位获胜。获胜者的战斗力值将变
成(自己的原战斗力值-对手的战斗力值+对手的能量值)。败者将死去。若两者战
斗力值一样,则同归于尽。
思考熊发现了很多油库里,他想知道通过互相战斗之后油库里中战斗力值+
能量值最高的一个可能到达多少。你能帮他们求出来吗?(假设除了考察的那只
油库里之外,其他油库里之间不会发生战斗)
【输入格式】
第一行是一个整数N,代表当前有多少油库里。
接下来的N行, 每一行有两个整数u,v, 代表这只油库里的战斗力值和能量值。
【输出格式】
输出一个整数,代表油库里中战斗力值+能量值最高的一个能达到多少。
【样例输入】
2
1 2
2 1
【样例输出】
4
【样例解释】
无。
【数据规模与约定】
对于100%的数据,1 ≤ u,v ≤ 10^9。

数据点编号 N= 数据点编号 N=
1 2 6 14989
2 984 7 21726
3 6168 8 100000
4 10470 9 100000
5 19168 10 100000

题解:

我们考虑贪心的策略:定义一只油库里的贡献值为它的能量值-它的战斗力,则对于每一只贡献值为负数的油库里,就干脆不要打(明显的,任何一只油库里如果打这只油库里战斗力值+能量值会减少)。由此将油库里分成两部分。

我们考察每一只油库里。

对于贡献值为正数的油库里,我们将它们按照战斗力大小从小到大进行排序,那么在所有贡献值为正数的油库里中,战斗力最大的油库里的答案最大,最大答案为它打败的所有油库里的贡献和+它的能量值+它的战斗力-它的贡献值,化简之后是它打败的所有油库里的贡献和+2*它的战斗力。

我们考虑如何快速求出它打败的所有油库里的贡献和,预处理一个前缀和数组$pre[i]$表示打败区间$[1,i]$之间的油库里能获得的贡献值即可。

对于贡献值为负数的油库里,我们只需要按照战斗力从大到小进行排序,那么同理,在贡献值为负数的油库里中,战斗力最大的油库里的答案最大。由刚开始的分析我们容易看出,我们不能打贡献值为负数的油库里,这只油库里只应该打贡献值为正数的油库里。

我们考虑如何求出这只油库里能够打败多少只贡献值为负数的油库里。

维护一个数组$dp[i]$表示如果要获得第$i$只油库里的贡献值所需要的最小初始战斗力是多少,我们容易得到递推方程$dp[i]=\max(dp[i-1],第i只油库里的战斗力-pre[i-1])$(第$i$只油库里至少需要打败前一只油库里才能获得贡献值)。

在处理每只贡献值为负数的油库里时,我们在$dp$数组里二分查找油库里的初始战斗力,然后到$pre$数组里去查询能获得的贡献值即可。

最后在这两种油库里的答案之间取一个最大值即可。


【问题描述】
现在你要实现一个文件系统,支持以下操作
cd Directory_Name
如果当前路径下有名为 Directory_Name 的文件夹,则进入该文件夹所对应
的路径,否则输出“No such directory!” 。
cd ..
如果当前路径存在父路径, 则返回父路径, 否则输出 “No parent directory!” 。
touch File_Name
如果当前目录下存在名为 File_Name 的文件则输出“File already exists!” ,
否则创建这样一个文件。
rm File_Name
如果当前目录下存在名为 File_Name 的文件则删除它,否则输出“No such
file!” 。
mkdir Directory_Name
如果在当前路径下存在名为 Directory_Name 的文件夹,则输出“Directory
already exists!” ,否则创建这样一个文件夹(当前路径不变) 。
rmdir Directory_Name
如果在当前路径下存在名为 Directory_Name 的文件夹,则删除之,否则输
出“No such directory!” 。
ls
列出当前路径下所有的文件和文件夹,每一项占一行,按创建的先后顺序给
出。采用以下形式输:
“Item_Name Type”
Type 为 <D>(文件夹)或<F>(文件)
注意:同一路径下文件与文件夹可以同名,但同一路径下文件与文件、文件
夹与文件夹不能同名。
初始时当前路径处于根路径下,无父路径。
【输入格式】
第一行为Q,表示有Q个操作。
接下来是Q行,每行输入为以上描述的操作之一。

【输出格式】
输出答案。
【样例输入】
3
mkdir standy
touch totalfrank
ls
【样例输出】
standy <D>
totalfrank <F>
【样例解释】
无。
【数据规模与约定】
对于100%的数据,1 ≤ Q ≤ 100,所有文件名字长度不超过200且均为小写
字母。

题解:

这应该是这三道题目中最简单的一道了。因为数据规模很小,所以顺着性子模拟就行了。

我的思路是开一个数组$tree[i]$表示创建时间为$i$的文件/文件夹的信息,当然不需要记录文件的父亲,因为不会cd到一个文件下面。然后对于每一个节点,我们存储两个map,模板分别为<int,pair<string,char> ><pair<string,char>,int>,分别对应修改时间->文件信息和文件信息->修改时间的映射。然后每次操作分别维护两个map和时间戳即可。创建文件夹时还需要维护它的父亲目录的创建时间。对于根目录,我们把它的父亲目录设置为一个无效的值然后在切换目录时判断一下就行了。

然后就是一个大模拟了。

#include<map>
#include<cstdio>
#include<string>
#include<iostream>
using namespace std;
typedef map<int,pair<string,char> > isc;//我不要写很长的一坨模板名 
typedef map<pair<string,char>,int> sci;
struct DICT{
    int par;//父亲节点的创建时间(数组下标) 
    sci f;//当前目录下xx名称+类型->目录下xx的创建时间(数组下标)。 
    isc t;//当前目录下xx创建时间(数组下标)->目录名称+类型。 
};
DICT tree[123456];//目录树
int now;//当前目录的数组下标 
int timestamp=1;//时间戳 
const int INVALID_PAR = 233333;//我们规定,当par==233333时,表示是根节点。
sci::iterator ret;//避免每次都创建一个迭代器 
int q;
string opt,na;
//剩下的就是模拟了233333... 
inline void cd(string par)
{
    if(par=="..")
    {
        if(tree[now].par==INVALID_PAR)cout<<"No parent directory!\n";
        else now=tree[now].par;
    }
    else
    {
        ret=tree[now].f.find(make_pair(par,'D'));
        if(ret==tree[now].f.end())cout<<"No such directory!\n";
        else now=ret->second;
    }
}
inline void touch(string name)
{
    ret=tree[now].f.find(make_pair(name,'F'));
    if(ret!=tree[now].f.end()){cout<<"File already exists!\n";return;}
    tree[now].f[make_pair(name,'F')]=timestamp;
    tree[now].t[timestamp]=make_pair(name,'F');
    ++timestamp;
}
inline void rm(string name)
{
    ret=tree[now].f.find(make_pair(name,'F'));
    if(ret==tree[now].f.end()){cout<<"No such file!\n";return;}
    int pos=ret->second;
    tree[now].f.erase(ret);
    tree[now].t.erase(pos);
}
inline void mkdir(string name)
{
    ret=tree[now].f.find(make_pair(name,'D'));
    if(ret!=tree[now].f.end()){cout<<"Directory already exists!\n";return;}
    tree[now].f[make_pair(name,'D')]=timestamp;
    tree[now].t[timestamp]=make_pair(name,'D');
    tree[timestamp].par=now;
    timestamp++;
}
inline void rmdir(string name)
{
    ret=tree[now].f.find(make_pair(name,'D'));
    if(ret==tree[now].f.end()){cout<<"No such directory!\n";return;} 
    int pos=ret->second;
    tree[now].f.erase(ret);
    tree[now].t.erase(pos);
}
inline void ls()
{
    for(isc::iterator it=tree[now].t.begin();it!=tree[now].t.end();++it)
        cout<<it->second.first<<" <"<<it->second.second<<">\n";
}
int main()
{
    tree[0].par=INVALID_PAR;
    #ifndef LOCAL
    freopen("nacumegu.in","r",stdin);
    freopen("nacumegu.out","w",stdout);
    #endif
    cin>>q;
    while(q--)
    {
        cin>>opt;
        if(opt!="ls")cin>>na;
        if(opt=="cd")cd(na);
        else if(opt=="touch")touch(na);
        else if(opt=="rm")rm(na);
        else if(opt=="mkdir")mkdir(na);
        else if(opt=="rmdir")rmdir(na);
        else ls();
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

垃圾代码,跑的贼慢。

推荐阅读