文章目录
B 都说小镇的切糕贵
链接:https://ac.nowcoder.com/acm/contest/3570/B
来源:牛客网
题目描述
“一刀建林流泪,两刀马云都得跪。”摆在你面前的一长条切糕,你想尝到切糕里面所有的果仁,什么核桃呀,杏仁呀,巴旦木呀…但因为切糕很贵,你要选取一段连续的切糕,使得你能吃到这份切糕里所有的果仁,切记切糕贵,所以要选取最短的长度并且要包含所有的果仁,这里的果仁可以简单的看做a果仁,b果仁,c果仁….,输出能包含所有果仁的最短长度。换句话说出现的果仁都要出现在你所选的区间里面,输出这个区间的最短长度。
输入描述: 第一行包含整数n(1≤n≤100 000)——切糕的长度。
第二行包含长度为n的字符串,它由英文字母表中的大写字母和小写字母组成。 输出描述: 输出一个整数,表示最小选取的长度。
示例1
输入
复制
1
A
输出
复制
1
示例2
输入
复制
4
qqqE
输出
复制
2
示例3
输入
复制
9
bcdddbddc
输出
复制
3
思路如下
题意是给一个长度为n(1 <= n <= 100 000)的字符串,其中既有大写字母又有小写字母组成,每个字母均代表一种切糕里面的果仁,让我们求出包含所有切糕的最小连续子序列。
首先我们应该求出所点有的 字母种类(这个可以通过map本身的性质来求出),其次我们要用尺取法的思想,去先假设从开头找到一个包含所有字母种类的一个序列,然后通过map的特性去维护这个序列的字母种类数量,在 尺取 的时候 一直保证字母种类不变,不断更新最优解即可
题解如下
//尺取法
#include<iostream>
#include<map>
#include<algorithm>
using namespace std;
const int Len = 100005;
char ar[Len];
int main()
{
int n;
cin>>n;
map<char,int> mp;
for(int i = 1;i <= n;i ++)
{
cin>>ar[i];
mp[ar[i]] ++;
}
int num = mp.size(); //切糕的种类
mp.clear();
int now = num; //now 当前切糕种类
int l = 1,r = 1;
while(mp.size() != num) //假定先取一个符合题意的字符串
{
mp[ar[r ++]] ++;
}
int ans = 1e9;
while(l <= n - num + 1 && r <= n + 1) //这里下标 l,r 的取值边界 要仔细考虑一下
{
if(now == num)
{
ans = min(ans , r - l);
mp[ar[l]] --;
if(mp[ar[l]] == 0) now --; //某种切糕出现的次数为0了,当前切糕种类减1
l ++;
}
if(now < num)
{
if(mp[ar[r]] == 0) now ++;
mp[ar[r ++]] ++;
}
}
cout<<ans;
return 0;
}
Subsequence
解题思路
这个题的意思很简单:就是给一个 n个 int元素的序列,优给了 一个值 s ,让求出 在所给的序列中 满足一个序列的和大于等于s的最短段子序。
思路:我们可以利用 尺取法的思路先 从序列的开头找到一个符合题意的子序列,再通过 两个 l 、r 相当于指针的东西不向区间左端缩进区间,并且在维护 在区间[l,r]的子序和始终大于等于s,在这个两个前提下,不断跟新最优解
题解如下
//尺取法 poj 3061
#include<iostream>
using namespace std;
const int Len = 100005;
int ar[Len];
int main()
{
int t;
cin>>t;
while(t --)
{
int n,s;
cin>>n>>s;
for(int i = 0;i < n;i ++)
cin>>ar[i];
int l = 0,r = 0;
int sum = 0;
int min_len = 1e9;
while(true)
{
while(sum < s && r < n) //尺取一个符合题意的从序列头部开始的区间
sum += ar[r ++];
if(sum < s) break; //如果已经没有符合题意的区间
if(min_len > r - l) //不断更新最优 子序长度
min_len = r - l;
sum -= ar[l ++]; //不断增加让sum减去一个ar[l]后的子序区间是否为符合 >= s 的序列
}
cout<<min_len<<endl;
}
return 0;
}
Jessica’s Reading Problem Poj3320
思路如下
这一题就是 与 第一个 b小镇的例题
一毛一样,这一题就相当于,把 那题中的 切糕果仁的种类 变成 知识的种类,,我们可以沿用上一题的思路,先用map的特性去求出 知识点的种类,在用尺取法 的思想 先从开头找到 一个包含所有 知识点的一个 区间,再通 l , r 两个相当于是指针的东西不断的 向 序列的 尾部不断遍历、尺取的时候 用map的特性不断维护保证整个过程 序列都包含所有 所有的知识点,这样把符合题意的所有最优 解保存下来就出现了结果
题解如下
//poj 3320
//尺取法
#include<iostream>
#include<map>
using namespace std;
const int Len = 1000005;
int ar[Len];
int main()
{
//int t;
//cin>>t;
//while(t --)
{
map<int,int> mp;
int n,s; //n个数字,s个知识点
cin>>n;
for(int i = 0;i < n;i ++)
{
cin>>ar[i];
mp[ar[i]] ++;
}
s = (int)mp.size();
mp.clear();
int min_len = 1e9;
int l = 0,r = 0;
while(mp.size() < s) //尺取法 开始先我们先取一个满足题意的从最左边开始的序列
{
mp[ar[r ++]]++;
}
int now = s; //当前 尺取 到的知识点个数(与上边的所叙述的原因有关)
while(l <= n - s && r <= n) //如果有s个知识点 下标 l 最大可以去到 n - s,下标 r 最大可以去到 n - 1 ,但是由于在while里面用的是 r++,所以为了在n-1的时候还可以循环 所以 r <= n
{
if(now == s)
{
min_len = min(min_len , r - l);
mp[ar[l]] --;
if(mp[ar[l]] == 0)
{
now --;
} //把某一种某种知识排除完了
l ++;
}
if(now < s) //现在的知识点少于要学的知识点
{
if(mp[ar[r]] == 0) now ++;
mp[ar[r ++]]++;
}
}
cout<<min_len<<endl;
}
return 0;
}
//牛客 切糕
//尺取法
#include<iostream>
#include<map>
#include<algorithm>
using namespace std;
const int Len = 100005;
char ar[Len];
int main()
{
int n;
cin>>n;
map<char,int> mp;
for(int i = 1;i <= n;i ++)
{
cin>>ar[i];
mp[ar[i]] ++;
}
int num = mp.size(); //切糕的种类
mp.clear();
int now = num; //now 当前切糕种类
int l = 1,r = 1;
while(mp.size() != num) //假定先取一个符合题意的字符串
{
mp[ar[r ++]] ++;
}
int ans = 1e9;
while(l <= n - num + 1 && r <= n + 1) //这里下标 l,r 的取值边界 要仔细考虑一下
{
if(now == num)
{
ans = min(ans , r - l);
mp[ar[l]] --;
if(mp[ar[l]] == 0) now --; //某种切糕出现的次数为0了,当前切糕种类减1
l ++;
}
if(now < num)
{
if(mp[ar[r]] == 0) now ++;
mp[ar[r ++]] ++;
}
}
cout<<ans;
return 0;
}
Max Sum (尺取法 最大子序和 hdu 1003)
解题思路 一
先说题意,这一题给一个含有n个元素的序列,序列中的元素有正有负,让求一个连续的 最大子序和。这一题对于用尺取法来说是不那么标准的,但是其中一定含有尺取法的思想在里面 ,首先对于这一题我们要进行分类讨论,一种情况是假定 序列的最大值是小于等于 0 那么这种的情况我们就 把这个值 作为最大的子序和就行了,而这个子序开始和结束的位置就是,这个值的下标;对于另一种情况:就需要 尺取法来(现在感觉起来和 用动态的方法的原理 好想相同)自己看题解吧太难叙述了
题解如下一
#include<iostream>
#include<algorithm>
using namespace std;
const int Len = 100005;
int sum[Len]; //前缀和
int main()
{
int t;
cin>>t;
int counter = 1;
while(t --)
{
int n;
cin>>n;
int max_val = -1e9; //初始化为一个不可能取到的极小值,来求数列中的最大值
int val,val_pos; //val_pos 使用来存数列中最大值的下标的
for(int i = 1;i <= n;i ++) //sum 求前缀和
{
cin>>val;
sum[i] = sum[i - 1] + val;
if(max_val < val)
{
max_val = val;
val_pos = i;
}
}
int st,ed; //st、ed 指向正在操作区间的开头和结尾
int ans = -1e9;
if(max_val <= 0) //分情况进行讨论1:最大值小于等有0
{
st = ed = val_pos;
ans = max_val;
}
else //分情况讨论2:
{
int l = 0,r = 1; //l,r相当于是指针,对给点的序列进行不断的判断
while(r <= n)
{
while(sum[l] <= sum[r] && r <= n)
{
if(ans < sum[r] - sum[l])
{
ans = sum[r] - sum[l];
st = l + 1;
ed = r;
}
r ++;
}
l = r; //当sum[r] < sum[l] 时更新区间最小值
r ++;
}
}
if(t)printf("Case %d:\n%d %d %d\n\n",counter ++,ans,st,ed);
else printf("Case %d:\n%d %d %d\n",counter ++,ans,st,ed);
}
return 0;
}
解题思路 二
这题我们还可以用动态规划来写,我么首先求出序列中每个位置的前缀和sum[i],我们要求的子序和就时就是区间的首、尾 前缀和之差,我通过一个变量 i 用for循环去遍历这个序列,在嘉定一个变量 min_sum 去存储下标 i 之前的最小值,在遍历的时候 如果 min_sum > sum[i],我们就把 min_sum的值更新为sum[i], 这样在循环的时候通过不断的 sum[i] - min_sum 得到的所有结果的最大值就是我们所需要的最大值
题解如下二
#include<iostream>
using namespace std;
const int Len = 100005;
struct Node
{
int val,pos;
}q[Len];
int main()
{
int t;
scanf("%d",&t);
int count = 0;
while(t --)
{
count ++;
int n;
scanf("%d",&n);
int sum[Len];
sum[0] = 0;
int tem;
int st = 0,ed = 0;
for(int i = 1;i <=n;i ++)
{
scanf("%d",&tem);
sum[i] = sum[i - 1] + tem;
}
int ans = -1500;
int min_sum = sum[0];
int min_pos = 0;
for(int i = 1;i <= n;i ++)
{
if(ans < sum[i] - min_sum)
{
ans = sum[i] - min_sum;
st = min_pos + 1;
ed = i;
}
if(min_sum > sum[i]) //更新前缀和最小值
{
min_sum = sum[i];
min_pos = i;
}
}
if(t)printf("Case %d:\n%d %d %d\n\n",count,ans,st,ed);
else printf("Case %d:\n%d %d %d\n",count,ans,st,ed);
}
return 0;
}