尺取算法!

尺取法
顾名思义,像尺子一样取一段。通过左右端点的伸缩来解题。

POJ3320 - Jessica’s Reading Problem


Jessica’s a very lovely girl wooed by lots of boys. Recently she has a problem. The final exam is coming, yet she has spent little time on it. If she wants to pass it, she has to master all ideas included in a very thick text book. The author of that text book, like other authors, is extremely fussy about the ideas, thus some ideas are covered more than once. Jessica think if she managed to read each idea at least once, she can pass the exam. She decides to read only one contiguous part of the book which contains all ideas covered by the entire book. And of course, the sub-book should be as thin as possible.

A very hard-working boy had manually indexed for her each page of Jessica’s text-book with what idea each page is about and thus made a big progress for his courtship. Here you come in to save your skin: given the index, help Jessica decide which contiguous part she should read. For convenience, each idea has been coded with an ID, which is a non-negative integer.

Input
The first line of input is an integer P (1 ≤ P ≤ 1000000), which is the number of pages of Jessica’s text-book. The second line contains P non-negative integers describing what idea each page is about. The first integer is what the first page is about, the second integer is what the second page is about, and so on. You may assume all integers that appear can fit well in the signed 32-bit integer type.

Output
Output one line: the number of pages of the shortest contiguous part of the book which contains all ideals covered in the book.

Sample Input
5
1 8 8 8 1
Sample Output
2

题意:
求所有种类元素都包括在内的最小区间长度。

#include<iostream>
#include<map>
#include<set>
#include<cstdio>
using namespace std;

typedef long long ll;
const ll N = 1e6 + 5;
int a[N];
set<int>o;
map<int, int>p;

int main()
{
	ll y;
	scanf("%lld", &y);
	for(ll i = 0; i < y; ++i)
	{
		scanf("%d", &a[i]);
		o.insert(a[i]);
	}
	ll num = o.size();	
	//因为set容器中不允许存在重复元素,所以可以利用此来求元素种类 
	ll sum = 0, t = 0, ans = y;
	for(ll i = 0; i < y; ++i)
	{
		if(p[a[i]]++ == 0)
			sum++;
		//计数元素种类 
		
		if(sum == num)
		{
			while(p[a[t]] > 1) 
				p[a[t++]]--;
			//当元素种类数达到题目要求(此时右端点已固定)
			//就可以开始从左端点舍去重复元素
			
			ans = min(ans, i - t + 1);
			//但是此时的区间可能并不是最小区间
			//将满足这个条件的区间像虫子一样往右蠕动遍历(左边丢,右边加) 
			//遍历完后即可得到最小区间长度 
		}
	}
	cout << ans << endl;
	return 0;
}

POJ 2100 - Graveyard Design

题:
King George has recently decided that he would like to have a new design for the royal graveyard. The graveyard must consist of several sections, each of which must be a square of graves. All sections must have different number of graves.
After a consultation with his astrologer, King George decided that the lengths of section sides must be a sequence of successive positive integer numbers. A section with side length s contains s2 graves. George has estimated the total number of graves that will be located on the graveyard and now wants to know all possible graveyard designs satisfying the condition. You were asked to find them.

Input
Input file contains n — the number of graves to be located in the graveyard (1 <= n <= 10^14 ).

Output
On the first line of the output file print k — the number of possible graveyard designs. Next k lines must contain the descriptions of the graveyards. Each line must start with l — the number of sections in the corresponding graveyard, followed by l integers — the lengths of section sides (successive positive integer numbers). Output line’s in descending order of l.

Sample Input
2030
Sample Output
2
4 21 22 23 24
3 25 26 27

题意:
输出所有的一个连续数的区间满足刚好等于给出的数据n。

法一:
这个用的是while里面套if和while,数据类型是结构体,要注意的是if里面,也就是相等的情况,是要进行一次左端点的移动的。
(之前犯了一个错误就是while>=n,因为我的两个while是在一起的,所以就导致while<n后,没有记录等于的情况就直接减掉了)

ps:这个是自己乱七八糟瞎写的,出来bug一大堆,自己看了好久改了好久最后还是麻烦一个大佬帮忙改好的QAQ

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath> 
using namespace std;

typedef long long ll;
const int N = 1e5 + 5;
struct ss
{
	int a, b;
}p[N];

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	
	ll n;
	while(scanf("%lld", &n) != EOF)
	{
		ll s = 0;
		int cur = 0;
		ll l = 1, r = 1;
		while(l*l <= n)
		{
			if(s == n)
			{
				p[cur].a = l;
				p[cur++].b = r;
				s -= l*l;
				l++;
			}
			while(s < n)
			{
				s += r*r;
				r++;
			}
			while(s > n)
			{
				s -= l*l;
				l++;
			}
			
		}
		cout << cur << endl;
		for(int i = 0; i < cur; ++i)
		{
			cout << p[i].b - p[i].a << " ";
			for(int j = p[i].a; j < p[i].b; ++j)
				cout << j << " ";
			cout << endl;
		}
	}
	
}

法二:
while里面套if,数据类型是vector里嵌套pair。

ps:这个是我搬的别的大佬写的,特别简单明了,tql。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;
#define  ll  long long
 
ll n;
typedef pair<int,int> P;
 
vector<P> p;
 
int main()
{
    cin >> n;
    ll l = 1,r = 1;
    ll sum = 0;
    while(l * l <= n)
    {
		if(sum == n) 
			p.push_back(P(r-l, l));
		if(sum <= n) 
			sum += r * r, r++;
		else 
			sum -= l*l, l++;
    }
    int cnt = p.size();
    sort(p.begin(),p.end());
 
    cout << cnt << endl;
 
    for(int i= cnt-1; i>= 0; i--)
    {
		printf("%d", p[i].first);
 		for(int j= p[i].second; j< p[i].second + p[i].first; j++)
			printf(" %d", j);
		printf("\n");
    }
    return 0;
}

HDU -5672 String

题:
There is a string S.S only contain lower case English character.(10≤length(S)≤1,000,000)
How many substrings there are that contain at least k(1≤k≤26) distinct characters?

Input
There are multiple test cases. The first line of input contains an integer T(1≤T≤10) indicating the number of test cases. For each test case:

The first line contains string S.
The second line contains a integer k(1≤k≤26).

Output
For each test case, output the number of substrings that contain at least k dictinct characters.

Sample Input
2
abcabcabca
4
abcabcabcabc
3
Sample Output
0
55

题意:
输出至少包含输入给的数据k个不同字母的区间数。

思路:
用尺取法先从左端开始,选一段最小包含k个不同字母的区间,然后它和后面所有字符区间的组合都满足这个条件,加起来,然后将这个最小区间慢慢往右挪动再加起来,如此一直到最末端即可得到答案。

#include<iostream>
#include<algorithm>
#include<string>
#include<set>
#include<map>
#include<cstdio>
#include<cstring>
#define bug(a) cout << a << "*" << endl;
using namespace std;

typedef long long ll;
const ll N = 1e6 + 5;
char a[N];

map<char, int>p;

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	
	int t;
	scanf("%d", &t);
	while(t--)
	{
		p.clear();
		scanf("%s", a);
		ll n;
		scanf("%d", &n);
		ll len = strlen(a);
		ll sum = 0;
		ll ans = 0;
		ll cur = 0;
		while(cur < len && sum < n)
		{
			if(p[a[cur++]]++ == 0)
				sum++;
		}
		if(sum == n)
			ans += len - cur + 1;
		//bug(ans);
		for(ll i = 0; i < len ; ++i)
		{
			if(p[a[i]]-- == 1)
			{
				sum--;
				while(cur < len && sum < n)
				{
					if(p[a[cur++]]++ == 0)
						sum++;
				}
			}
			if(sum == n)
				ans += len - cur + 1;
			//bug(ans);
		}
		printf("%lld\n", ans);
	}
	return 0;
 } 

HDU5178 - pairs

题:
John has n points on the X axis, and their coordinates are (x[i],0),(i=0,1,2,…,n−1). He wants to know how many pairs<a,b> that |x[b]−x[a]|≤k.(a<b)

Input
The first line contains a single integer T (about 5), indicating the number of cases.
Each test case begins with two integers n,k(1≤n≤100000,1≤k≤10^9).
Next n lines contain an integer x[i] (−10^9 ≤ x[i] ≤ 10^9), means the X coordinates.

Output
For each case, output an integer means how many pairs<a,b> that |x[b]−x[a]|≤k.

Sample Input
2
5 5
-100
0
100
101
102
5 300
-100
0
100
101
102
Sample Output
3
10

题意:
给出n个x轴上的点的x坐标和整数k,求有多少对<a,b>满足|x[b]−x[a]|≤k (a<b)。

思路:
遍历每个坐标利用二分函数找到最小的满足这个条件的数,将结果加起来并输出。

#include<iostream>
#include<cmath>
#include<algorithm>
#define bug(a) cout << a << "*" << endl;
using namespace std;

typedef long long ll;
const int N = 1e5 + 5;

ll a[N];

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	
	int T;
	cin >> T;
	while(T--)
	{
		ll n, k, ans = 0;
		cin >> n >> k;
		bool flag = 0;
		for(int i = 0; i < n; ++i)
			cin >> a[i];
		sort(a, a+n);
		int o, len;
		for(int i = 0; i < n; ++i)
		{
			o = upper_bound(a+i, a+n, a[i]+k) - a;
			if(a[o] == a[i]+k)
				len = o - i;
			else
				len = o - i - 1;
			ans += len;
			//bug(len);
		}
		cout << ans << endl;
		
	}
	return 0;
 } 

HDU - 6119 小小粉丝度度熊

题:
度度熊喜欢着喵哈哈村的大明星——星星小姐。

为什么度度熊会喜欢星星小姐呢?

首先星星小姐笑起来非常动人,其次星星小姐唱歌也非常好听。

但这都不是最重要的,最重要的是,星星小姐拍的一手好代码!

于是度度熊关注了星星小姐的贴吧。

一开始度度熊决定每天都在星星小姐的贴吧里面签到。

但是度度熊是一个非常健忘的孩子,总有那么几天,度度熊忘记签到,于是就断掉了他的连续签到。

不过度度熊并不是非常悲伤,因为他有m张补签卡,每一张补签卡可以使得某一忘签到的天,变成签到的状态。

那么问题来了,在使用最多m张补签卡的情况下,度度熊最多连续签到多少天呢?

Input
本题包含若干组测试数据。

第一行两个整数n,m,表示有n个区间,这n个区间内的天数,度度熊都签到了;m表示m张补签卡。

接下来n行,每行两个整数(l[i],r[i]),表示度度熊从第l[i]天到第r[i]天,都进行了签到操作。

数据范围:
1<=n<=100000
0<=m<=1000000000
0<=l[i]<=r[i]<=1000000000

注意,区间可能存在交叉的情况。

Output
输出度度熊最多连续签到多少天。

Sample Input
2 1
1 1
3 3
1 2
1 1
Sample Output
3
3

Hint
样例一:度度熊补签第2天,然后第1天、第二天和第三天都进行了签到操作。
样例二:度度熊补签第2天和第3天。

题意:
给出n个区间,这n个区间是度度熊连续签到的天,给出m张补签卡,求度度熊利用这m张补签卡后得到的最大连续签到天数。

思路:
因为区间可能存在交叉的情况,所以先用一个数组存输入的数据,然后将区间排序并且用另一个数组去存合并有重合签到天后的区间,再用一个数组去存各区间之间的空隙。
遍历各区间用补签卡后的连续天数,取最大连续天数。

注意:后面用补签卡算连续天数有一些细节,之前卡了很久,其实静下心来认真捋一捋就出来了QAQ

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#define bug(a) cout << a << "*" << endl; 
using namespace std;

typedef long long ll;
const int N = 1e5 + 5;
struct ss
{
	ll a, b;
}x[N], y[N];

ll u[N];

bool cmp(ss w, ss v)
{
	if(w.a == v.a)
		return w.b < v.b;
	else
		return w.a < v.a;
}

int main()
{
	int n;
	ll m;
	while(cin >> n >> m)
	{
		for(int i = 0; i < n; ++i)
			scanf("%lld %lld", &x[i].a, &x[i].b);
		sort(x, x+n, cmp);
		int cur = 0;
		y[cur] = x[0];
		for(int i = 1; i < n; ++i)
		{
			if(x[i].a <= y[cur].b + 1)
			{
				y[cur].b = max(y[cur].b, x[i].b);
				//cout << y[cur].b  << "  66" << endl;
			}
			else
			{
				cur++;
				y[cur] = x[i];
			}
				
		}
		/*for(int i = 0; i <= cur; ++i)
			cout << y[i].a << " " << y[i].b << endl;
		*/
		for(int i = 1; i <= cur; ++i)
		{
			u[i-1] = y[i].a - y[i-1].b -1; 
		}
		ll ans = y[0].b - y[0].a + 1 + m;
		if(n > 1)
		for(int i = 0; i <= cur; ++i)
		{
			ll s = 0;
			int e = i;
			while(s <= m && e < cur)
			{
				if(s + u[e] <= m)
					s += u[e];
				else
					break;
				e++;
			}
			ans = max(y[e].b-y[i].a+1+m-s, ans);
			//bug(y[e-1].b);
			
		 }
		 printf("%lld\n", ans); 
	}
	
	
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值