2024.08.10美团(有困难题)
1. 小美的密码
小美准备登录美团,需要输入密码,小美忘记了密码,只记得密码可能是 n个字符串中的一个。
小美会按照密码的长度从小到大依次尝试每个字符串,对于相同长度的字符串,小美随机尝试,并且相同的密码只会尝试一次。
小美想知道,她最少需要尝试多少次才能登录成功,最多需要尝试多少次才能登录成功。
哈希找规律
int main() {
int n;
cin>>n;
string target;
cin>>target;
map<int,int> m;//记录对应长度字符串个数
unordered_set<string> st;//避免重复
for(int i=0;i<n;i++){
string cur;
cin>>cur;
if(st.count(cur)) continue;
st.insert(cur);
m[cur.size()]++;
}
int res = 0;
for(auto [key,val]:m){
if(key==target.size()) break;
res += val;
}
cout<<res+1<<" "<<res+m[target.size()];
return 0;
}
2. 小美的数组删除
小美有一个长度为 n 的数组 a1,a2,....,an ,他可以对数组进行如下操作:
● 删除第一个元素 a1,同时数组的长度减一,花费为 x。
● 删除整个数组,花费为 k*MEX(a) (其中 MEX(a) 表示 a 中未出现过的最小非负整数。例如 [0,1,2,4] 的 MEX 为 3 )。
小美想知道将 a 数组全部清空的最小代价是多少,请你帮帮他吧。
顺序遍历同时维护快速查询的最小非负整数(使用动态规划+队列+哈希)
int main() {
int T;
cin>>T;
while(T--){
int n,k,x;
cin>>n>>k>>x;//元素数量、删除整个数组的花费系数、删除单个元素的花费
vector<int> nums(n);
for(int i=0;i<n;i++)
cin>>nums[i];
int res= INT_MAX;
queue<int> q;
for(int i=0;i<=n;i++)
q.push(i);
unordered_set<int> st;//记录已经出现的整数
vector<int> dp(n);//dp[i]表示以i为开头,之后数组不存在的最小非负整数
for(int i=n-1;i>=0;i--){
st.insert(nums[i]);
while(st.count(q.front()))
q.pop();
dp[i] = q.front();
}
int delpre = 0;
for(int i=0;i<n;i++){
res = min(res,delpre+k*dp[i]);
delpre+=x;
}
cout<<res;
}
return 0;
}
3. 小美的彩带
小美的彩带是由一条长度为 n 的彩带一直无限循环得到的,彩带的每一个位置都有一个颜色,用 ai 表示。
因此当 i>n 时,ai = ai-n 。小美每次会从左往后或从右往左剪一段长度为 x 的彩带,她想知道她每次剪下来的彩带有多少种颜色。
树状数组
离线查询,将查询点与插入点一同排序,查询按区间右侧点排
同时需要将将裁剪转化为查询区间
每次查询的时候,插入相同的时候,将上一个位置删除掉,保证[l,r] = sum(r)-sum(l-1),这样只会计算出现过一次的
vector<int> tree;
int lowbit(int x){
return x&(-x);
}
void updata(int i,int k){
while(i<=tree.size()){
tree[i] += k;
i+=lowbit(i);
}
}
int getsum(int i){
int res = 0;
while(i>0){
res+=tree[i];
i-=lowbit(i);
}
return res;
}
int main() {
int n, q;//彩带长度和切带次数
cin>>n>>q;
tree.resize(2*n+1);//树状数组
vector<int> color(n);//记录每个位置的颜色
for(int i=0;i<n;i++)
cin>>color[i];
vector<char> dir(q);
vector<int> len(q);
for(int i=0;i<q;i++)
cin>>dir[i]>>len[i];
//首先要将查询转化为查询区间,将查询区间与插入点(颜色)一同排序
//按查询区间右侧升序排序,同时插入点在前面
vector<vector<int>> sorted;
for(int i=0;i<2*n;i++){//放入插入点(颜色,位置,以及插入点标识)
sorted.push_back({color[i%n],i+1,-1});//1~2n ,这里循环进行映射,将一段拓展到两段长度
}
int l = 1; int r = 2*n;//左端和右端的初始位置
for(int i=0;i<dir.size();i++){//放入查询区间(左端,右端,查询下标)
if(dir[i]=='L'){
while(l>n) l = l-n;//重置一下左端
int curl = l; int curr = l+len[i]-1; //这里保证如果裁剪长度不超过n,查询时不会出现下标溢出
sorted.push_back({curl,min(curr,2*n),i});
l = curr+1;//左端后移一位
}
else if(dir[i]=='R'){
while(r<=n) r = r+n;//重置一下右端
int curl = r-len[i]+1; int curr = r;//这里保证如果裁剪长度不超过n,查询时不会出现下标溢出
sorted.push_back({curl,max(1,curr),i});
r = curl-1;//右端前移一位
}
}
sort(sorted.begin(),sorted.end(),[&](auto &a,auto &b){
if(a[1]==b[1]) return a[2]<b[2];//插入点在前,查询点在后
return a[1]<b[1];
});
vector<int> res(q);
unordered_map<int,int> m;//记录某个颜色上次出现位置
for(auto &query:sorted){//遍历插入点和查询点
if(query[2]==-1){//如果是插入点,对树状数组进行更新
if(m.count(query[0])){//如果该颜色存在,先剔除掉,反正对于后面的查询,该点不重要
updata(m[query[0]],-1);
}
updata(query[1],1);
m[query[0]] = query[1];//更新位置
}
else{//如果是查询点,则进行查询
int idx = query[2];//第几个查询结果
res[idx] = getsum(query[1])-getsum(query[0]-1);
}
}
for(auto num:res)
cout<<num<<endl;
return 0;
}