尺取法简单应用

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;
}
发布了73 篇原创文章 · 获赞 100 · 访问量 2699

猜你喜欢

转载自blog.csdn.net/qq_34261446/article/details/103791920