第三周集训

牛客补题

拆分正整数

游游拿到了一个n,她准备将n拆分成两个正整数a和b,满足 a+b=n且a∗b是3的倍数。你能告诉游游有多少种拆分方式吗?

输入描述:

一个正整数n。
1≤n≤10 的14次方

sample

输入

10

输出

6

说明

10=1+9
10=3+7
10=4+6
10=6+4
10=7+3
10=9+1
共有以上6种拆分方法满足a*b是3的倍数。

代码

#include <iostream>
using namespace std;
int main() {
  long long int n;
  cin >> n;
    long long int t=n/3;
    if(n%3==0){
        cout<<t-1<<endl;
    }
    else{
        cout<<2*t<<endl;;
    }
}

一、 双指针和尺取法 点击看详细知识点

1、Subsequence—子序列问题

题面翻译

题目描述:

有一个长度为 N N N的正整数序列 ( 10 < N < 100 , 000 ) (10 < N < 100,000) (10<N<100,000),每一个数字都小于等于 10000 10000 10000,再给定一个正整数 S S S ( S < 100 , 000 , 000 ) (S < 100,000,000) (S<100,000,000),试求一个连续子序列,使得该序列的数字之和大于或等于 S S S,并且要求该子序列尽量短。

输入输出格式

输入格式:

输入有多组数据。每组数据第一行为两个正整数 N N N S S S,中间用空格隔开。第二行给出这个序列,每两个整数之间用空格隔开。输入文件以EOF结尾。当这个连续子序列不存在时输出0

输出格式:

对于每组数据,输出一个整数,代表满足要求的最短子序列的长度。每组数据输出占一行。

输入输出样例

输入样例:

10 15
5 1 3 5 10 7 4 9 2 8
5 11
1 2 3 4 5

输出样例:

2
3

题目描述

PDF

分析

尺取法 (O(n)).
用s和t表示子序列的起点和终点,sum表示子序列[s,t)的和,ans表示最短的符合要求的子序列长度。

  1. 将t向后移动,并更新sum,直到sum≥S∣∣t=N,如果t=N&&sum<S,就退出,否则更新ans。

  2. 将s向后移动,并更新sum,再回到步骤1。

代码

#include <iostream>
#include <algorithm>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=1e5+5;
ll s[N];
int main()
{
	ll n;	
	cin>>n;
	while(n--){
		ll a,b;
		ll sum=0;
		cin>>a>>b;//输入序列长度 和要找的目标值
		memset(s,0,sizeof(s)); //每次循环前清空数组
	for(ll i = 1;i <= a;i++){
		cin>>s[i];//输入序列 
	}
	int j=1,i=1,ans=a+1;
	while(1){
		while(sum<b&&j<=a){
			sum+=s[j];
			j++;
		}
		if(sum>=b){
			ans=min(ans,j-i);//最小长度 
		    sum-=s[i];
		    i++; 
		}
		else break;	
		}
		if(ans>a) cout<<"0"<<endl;
     	else cout<<ans<<endl;	
	}    
    return 0;
}

2、Bound Found

Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 5078 Accepted: 1617 Special Judge

Description

Signals of most probably extra-terrestrial origin have been received and digitalized by The Aeronautic and Space Administration (that must be going through a defiant phase: “But I want to use feet, not meters!”). Each signal seems to come in two parts: a sequence of n integer values and a non-negative integer t. We’ll not go into details, but researchers found out that a signal encodes two integer values. These can be found as the lower and upper bound of a subrange of the sequence whose absolute value of its sum is closest to t.

You are given the sequence of n integers and the non-negative target t. You are to find a non-empty range of the sequence (i.e. a continuous subsequence) and output its lower index l and its upper index u. The absolute value of the sum of the values of the sequence from the l-th to the u-th element (inclusive) must be at least as close to t as the absolute value of the sum of any other non-empty range.
Input

The input file contains several test cases. Each test case starts with two numbers n and k. Input is terminated by n=k=0. Otherwise, 1<=n<=100000 and there follow n integers with absolute values <=10000 which constitute the sequence. Then follow k queries for this sequence. Each query is a target t with 0<=t<=1000000000.
Output

For each query output 3 numbers on a line: some closest absolute sum and the lower and upper indices of some range where this absolute sum is achieved. Possible indices start with 1 and go up to n.

Sample

InputOutput
5 1
-10 -5 0 5 10
35 4 4
10 2
-9 8 -7 6 -5 4 -3 2 -1 0
55 2 8
119 1 1
15 2
-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
1515 1 15
10015 1 15
0 0

分析

题意:给你n个数,让你找一个子区间使得该子区间之和的绝对值和t的差尽可能小,输出该区间的和,以及区间的左右端点。

题解:因为数组中的数有正有负,故不满足尺取单调性的要求,我们可以考虑求前缀和,因为两个前缀和相减对应的是任意一个区间的和,因为有绝对值,所以不用考虑端点的先后顺序,我们将所有前缀和从小到大排序,然后按照尺取遍历即可。

代码

#include<math.h>
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
int a[100005],l,r,n,k;
ll sm,ans,t;
const long long N = 1e19;
ll abss(ll x)
{
	if(x<0)
		x=-x;
	return x;
}//绝对值函数
struct node
{
	ll s;//数字 
	ll p;//位置 
}sum[100005];
bool comp(node a,node b)
{
	return a.s<b.s;//升序输出 
}
int main()
{
	int i;
	while(cin >> n >> k && n && k)
	{
		sum[0].p=sum[0].s=0;
		for(i=1;i<=n;i++){
			scanf("%d",&a[i]);
			sum[i].s = sum[i-1].s+a[i]; //前缀和 
			sum[i].p = i;
		}
			
		sort(sum , sum+n+1 , comp);//将前缀和按照数的大小升序输出 
		while(k--) 
		{
			ans=N;
			cin >> t;
			l = 0;
			r = 1; 
			ll ans1;
		    int ls,rs;
			while(l <= n && r <= n)
			{
				ll tmp = abss( sum[l].s - sum[r].s);
				ll tmp1 = abss(tmp - t);
				if( tmp1 < ans)
				{
					ans = tmp1;//tmp1更接近t 
					ans1 = tmp;//此时的区间差 
					ls = sum[l].p;//左边界的位置 
					rs = sum[r].p;//右边界的位置 
				}
				if(tmp > t) l++; 
				else if(tmp < t) r++;
				else break;
				if(l==r) r++;
			}
			if(ls > rs) swap(ls,rs);
			printf("%lld %d %d\n",ans1,ls+1,rs);
		}
	}
	return 0;
}

前缀和

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

void solve(){
    int n;
    cin >> n;
    int sum[100005] = {0};
    for (int i = 1; i <= n;i++){
        int x;
        cin >> x;
        sum[i] = sum[i - 1] + x;
    }
    // sum(l,r) = sum[r] - sum[l - 1] 先O(n)预处理前缀和,O(1)计算每个区间和
    int q;
    cin >> q;
    while(q--){
        int l, r;
        cin >> l >> r;
        cout << sum[r] - sum[l - 1] << endl;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    //cin >> t;
    while(t--){
        solve();
    }
    return 0;
}

关于ios::sync_with_stdio(false);和cin.tie(0);cout.tie(0);

http://t.csdn.cn/poBjm

3、# A-B 数对

题目背景

出题是一件痛苦的事情!

相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!

题目描述

给出一串正整数数列以及一个正整数 C C C,要求计算出所有满足 A − B = C A - B = C AB=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。

输入格式

输入共两行。

第一行,两个正整数 N , C N,C N,C

第二行, N N N 个正整数,作为要求处理的那串数。

输出格式

一行,表示该串正整数中包含的满足 A − B = C A - B = C AB=C 的数对的个数。

样例 #1

样例输入 #1

4 1
1 1 2 3

样例输出 #1

3

提示

对于 75 % 75\% 75% 的数据, 1 ≤ N ≤ 2000 1 \leq N \leq 2000 1N2000

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10^5 1N2×105 0 ≤ a i < 2 30 0 \leq a_i <2^{30} 0ai<230 1 ≤ C < 2 30 1 \leq C < 2^{30} 1C<230

2017/4/29 新添数据两组

分析

我们考虑题目要求求出所有A-B=C的数对,我们可以先将原数组排序,然后就会发现每个数
A,对应的数B一定是一段连续的区间。

  1. 然后我们再考虑如何去找到这个区间。
  2. 我们显然是要找到这个连续区间的左端点和右端点。
  3. 考虑到排序之后序列的有序性,我们枚举每个数,他们的左端点和右端点都是单调不降的,因此我们可以用 two-pointers 也就是双指针来维护这个东西。
  4. 具体的实现就是,我们维护两个右端点r1 , r2,每次r1右移到a[r1] - a[l] <= c的最后位置的下一位, r2右移到满足a[r2] - a[l] < c最后一位.
  5. 也就是说, 此时如果a[r2] - a[l] == c && a[r1 - 1] - a[l] == c,中间的那一段一定都是满足条件的,我们让ans += r1 - r2即可。

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int N = 2e5 + 10;
int n , c;
int a[N];

int main () 
{
	cin >> n >> c;
	for(int i = 1 ; i <= n ; i ++)
    cin >> a[i];
	sort(a + 1 , a + 1 + n);//一定要排序
	int l = 1, r1 = 1 , r2 = 1;
	ll ans = 0;
	for(l = 1 ; l <= n ; l ++) {
		while(r1 <= n && a[r1] - a[l] <= c) r1 ++;
		while(r2 <= n && a[r2] - a[l] < c ) r2 ++;
		if(a[r2] - a[l] == c && a[r1 - 1] - a[l] == c && r1 - 1 >= 1) 	
			ans += r1 - r2;
	}
	cout << ans;
	return 0;
}

4、Problem A: Unique Snowflakes—最长不同连续子序列

题目 描述

Emily the entrepreneur has a cool business idea: packaging and sellingsnowflakes. She has devised a machine that captures snowflakes as theyfall, and serializes them into a stream of snowflakes that flow, one byone, into a package. Once the package is full, it is closed andshipped to be sold.
The marketing motto for the company is "bags of uniqueness."To live up to the motto, every snowflake in a packagemust be different from the others. Unfortunately, this iseasier said than done, because in reality, many of the snowflakesflowing through the machine are identical. Emily would like toknow the size of the largest possible package of unique snowflakesthat can be created. The machine can start filling the packageat any time, but once it starts, all snowflakes flowing fromthe machine must go into the package until the package is completedand sealed. The package can be completed and sealed before all ofthe snowflakes have flowed out of the machine.

Input Specification

The first line of input contains one integer specifying the number oftest cases to follow. Each test case begins with a line containingan integer n, the number of snowflakes processed by the machine.The following n lines each contain an integer (in the range0 to 10^9, inclusive) uniquely identifyinga snowflake. Two snowflakes are identified by the same integer ifand only if they are identical.The input will contain no more than one million total snowflakes.
Sample Input
1
5
1
2
3
2
1

Output Specification

For each test case output a line containing single integer, the maximumnumber of unique snowflakes that can be in a package.
Output for Sample Input

3

题意:

给n个数,求最长不同连续子序列。n<=1e6。

解题过程:

1.记录数据存于数组

2.用左右指针l和r指向这段连续区间

3.右指针往右走,如果遇到没有存在于set集合的数就插入集合

否则左指针往右走,逐渐删去set里的数据,不断更新答案,一直保留最大值。

代码

#include<iostream>
#include<set>
using namespace std;
const int maxn = 1e6 + 5;
int A[maxn];
int main(){
    int T,n;
    cin>>T;
    while(T--){
        cin>>n;
        for(int i = 0; i < n; i++)
		cin>>A[i];
        set<int> s;
        int i = 0, j = 0,ans = 0;
        while(j < n){
            while(j<n&&!s.count(A[j])) 
			s.insert(A[j++]);
            ans=max(ans,j-i);
            s.erase(A[i++]);
        }
        cout<<ans<<endl;
    }
    return 0;
}

数组去重

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

void solve(){
    int n;
    cin >> n;
    int a[100005];
    for (int i = 1; i <= n;i++){
        cin >> a[i];
    }
    sort(a + 1, a + n + 1);
    int i = 1, j = 1;
    while (j < n){
        j++;
        if(a[j] != a[i]){
            i++;
            a[i] = a[j];
        }   
    }
    for (int k = 1; k <= i;k++){
        cout << a[k] << " ";
    }
    cout << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    //cin >> t;
    while(t--){
        solve();
    }
    return 0;
}
/*
6 
1 2 2 3 3 3

1 2 3
*/

二分法专题练习

— 实数二分模板

在这里插入图片描述

小数二分模板

在这里插入图片描述

[USACO07MAR] Monthly Expense S

题目描述

Farmer John is an astounding accounting wizard and has realized he might run out of money to run the farm. He has already calculated and recorded the exact amount of money (1 ≤ moneyi ≤ 10,000) that he will need to spend each day over the next N (1 ≤ N ≤ 100,000) days.

FJ wants to create a budget for a sequential set of exactly M (1 ≤ M ≤ N) fiscal periods called “fajomonths”. Each of these fajomonths contains a set of 1 or more consecutive days. Every day is contained in exactly one fajomonth.

FJ’s goal is to arrange the fajomonths so as to minimize the expenses of the fajomonth with the highest spending and thus determine his monthly spending limit.

给出农夫在n天中每天的花费,要求把这n天分作m组,每组的天数必然是连续的,要求分得各组的花费之和应该尽可能地小,最后输出各组花费之和中的最大值

输入格式

Line 1: Two space-separated integers: N and M

Lines 2…N+1: Line i+1 contains the number of dollars Farmer John spends on the ith day

输出格式

Line 1: The smallest possible monthly limit Farmer John can afford to live with.

样例 #1

样例输入 #1

7 5
100
400
300
100
500
101
400

样例输出 #1

500

提示

If Farmer John schedules the months so that the first two days are a month, the third and fourth are a month, and the last three are their own months, he spends at most $500 in any month. Any other method of scheduling gives a larger minimum monthly limit.

分析

问n个数分成m段,并且每段的和的最大值最小

最大值最小,最小值最大……暴露出这题是二分答案,这也是做题时的一个小技巧,快速分辨出这题用什么方法
手动模拟出样例如何求出500
根据样例可得,7个数要分成五段,并且每段连续

左边界取7个元素中最大的一个,也就是500;(如果不是最大的一个数,根本不能分)

右边界为7个元素之和,也就是1901(最大7个数合成一段)

100400300100500101400

此时mid为(Left+Right)/2,也就是1200(向下取整)

这时,判断每段和为1200是否合法

  1. 100+400+300+100=900
  2. 500+101+400=1001

只分成了两段,不合法,为了分的段数更多,需要将每段和缩小,也就是调整左边界,此时 Right为 mid−1,1199

继续分,(1999+500)/2=849
mid=(1999+500)/2=849(任然向下取整)

  1. 100+400+300=800
  2. 100+500+101=701
  3. 400

三段,不合法,继续分

Right=mid−1=849−1=848
mid=(500+848)/2=67

  1. 100+400=500
  2. 300+100=400
  3. 500
  4. 101+400=501

四段,继续

Right=mid−1=674−1=673
mid=(500+673)/2=586

  1. 100+400=500
  2. 300+100=400
  3. 500
  4. 101+400=501

四段,继续

Right=mid−1=586−1=585
mid=(500+585)/2=542

  1. 100+400=500
  2. 300+100=400
  3. 500
  4. 101+400=501

……………

直到——

Right=500,
Left=500

100+400=500
2. 300+100=400
3. 500
4. 101
5. 400

这时,正好分为五段,并且每段最大值最小

代码

#include<iostream>
#include<algorithm>
using namespace std;
int n, m, a[100010], low = 0, high = 0, mid;
int judge(int mid)
{
	int sum = 0, ans = 1;//开始为整体为一组,//sum是累加当前的钱,cnt是要分成几组
	for (int i = 1; i <= n; i++)
	{
		if (sum + a[i] <= mid)//如果加上这个月的花费还比枚举到的开销小
			sum += a[i];//那就继续加
		else
		{
			sum = a[i];//不然就要分一组出来
			ans++;
		}
	}
	if (ans > m)
		return 1;//组数偏多mid偏小 low=mid+1
	else
		return 0;//组数偏少mid偏大
}
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		low = max(a[i], low);//预处理,最小开销至少跟花费最大的那个月一样
		high += a[i];//最大开销可能是所有的月加起来
	}
	while (low <= high)
	{
		mid = low + (high - low) / 2;
		if (judge(mid))//
			low = mid + 1;
		else
			high = mid - 1;
			
	}
	cout << low << endl;
	return 0;
}

# [USACO06DEC] River Hopscotch S

题目描述

Every year the cows hold an event featuring a peculiar version of hopscotch that involves carefully jumping from rock to rock in a river. The excitement takes place on a long, straight river with a rock at the start and another rock at the end, L units away from the start (1 ≤ L ≤ 1,000,000,000). Along the river between the starting and ending rocks, N (0 ≤ N ≤ 50,000) more rocks appear, each at an integral distance Di from the start (0 < Di < L).

To play the game, each cow in turn starts at the starting rock and tries to reach the finish at the ending rock, jumping only from rock to rock. Of course, less agile cows never make it to the final rock, ending up instead in the river.

Farmer John is proud of his cows and watches this event each year. But as time goes by, he tires of watching the timid cows of the other farmers limp across the short distances between rocks placed too closely together. He plans to remove several rocks in order to increase the shortest distance a cow will have to jump to reach the end. He knows he cannot remove the starting and ending rocks, but he calculates that he has enough resources to remove up to M rocks (0 ≤ M ≤ N).

FJ wants to know exactly how much he can increase the shortest distance *before* he starts removing the rocks. Help Farmer John determine the greatest possible shortest distance a cow has to jump after removing the optimal set of M rocks.

奶牛跳房子:从N块石头中移除M块,使得间距最小值最大。

输入格式

Line 1: Three space-separated integers: L, N, and M

Lines 2…N+1: Each line contains a single integer indicating how far some rock is away from the starting rock. No two rocks share the same position.

输出格式

Line 1: A single integer that is the maximum of the shortest distance a cow has to jump after removing M rocks

样例 #1

样例输入 #1

25 5 2
2
14
11
21
17

样例输出 #1

4

提示

Before removing any rocks, the shortest jump was a jump of 2 from 0 (the start) to 2. After removing the rocks at 2 and 14, the shortest required jump is a jump of 4 (from 17 to 21 or from 21 to 25).

分析

1、 由于n是中间的石头数,并未包含终点的石头,所以要加入到距离队列中。题目的意思很简单,就是最多移走m块石头,使得选手跳跃的最短距离最大即可。
2. 由于二分的数组需要有序,所以需要用sort排好。
3. 二分距离,因为答案在二分的左侧(较小数中的最大值),所以需要及时记录答案(即mid)。
4.**判断函数:**统计出当距离为s时总共需要移走几个石头。具体思路用到贪心,详见注释。有两种写法:一个是直接统计出需要移走的数量,一个是先统计留下来的石头的总数,再在return时用总数来算出移走的数量。
5. 简单的思路:从起点出发,先选定一段距离mid,若前面的石头B与你所站着的石头A的距离小于mid,就把B搬掉,记录一下;如果不,就把B留下,再跳到石头B上。照这个步骤多次循环后,如果搬掉的石头多了,就把距离mid定小点;如果少了,就把mid定大点。

代码

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


int t,m,n,r=0,l;
int a[50005];
bool check(int mid){
	int b = 0,j = 0;//b是搬走的石头数,j为第i个石头的上一块石头 
	for(int i=1;i<=n+1;i++){
		if(a[i]-a[j]<mid){//两个石头之间的距离小于中间值 
			b++;//石块数++
		}
		else
		j=i;//下一块石头的上一块是他
	}
	return b<=m;//判断有没有大于规定
}
int main()
{
	cin>>t>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	sort(a,a+n+1);//排序,排序,排序!!! 
	r=t+1;//规定边界,让以后不用特判终点的石头的情况 
	a[n+1]=t;//这样方便终点的石头调用
	while(l + 1 < r){
		int mid=l+(r-l)/2;
		if(check(mid)){
			l = mid;//还可以继续放石头
		}
		else
		r=mid;//少几块石头
	}
	cout<< l<<endl;
	return 0;
}

# 进击的奶牛

题目描述

Farmer John 建造了一个有 N N N 2 ≤ N ≤ 1 0 5 2 \leq N \leq 10 ^ 5 2N105) 个隔间的牛棚,这些隔间分布在一条直线上,坐标是 x 1 , x 2 , ⋯   , x N x _ 1, x _ 2, \cdots, x _ N x1,x2,,xN 0 ≤ x i ≤ 1 0 9 0 \leq x _ i \leq 10 ^ 9 0xi109)。

他的 C C C 2 ≤ C ≤ N 2 \leq C \leq N 2CN)头牛不满于隔间的位置分布,它们为牛棚里其他的牛的存在而愤怒。为了防止牛之间的互相打斗,Farmer John 想把这些牛安置在指定的隔间,所有牛中相邻两头的最近距离越大越好。那么,这个最大的最近距离是多少呢?

输入格式

1 1 1 行:两个用空格隔开的数字 N N N C C C

2 ∼ N + 1 2 \sim N+1 2N+1 行:每行一个整数,表示每个隔间的坐标。

输出格式

输出只有一行,即相邻两头牛最大的最近距离。

样例 #1

样例输入 #1

5 3
1
2
8
4
9

样例输出 #1

3

分析

本文主要分析二分答案过程中,对于上下界的理解和实现。

我写二分答案时,上下界没有写好导致Wrong Answer。又看到讨论区有人的上下界写错了,于是TLE。所以决定仔细分析一下二分的上下界问题。

众所周知,二分答案时,需要有上界、下界、中点。分别命名为left,right,mid。

此题中,要求最大值,因此有重要结论:若mid为解,则最优解一定属于[mid, right]。若mid不为解,则最优解一定属于[right, mid)。对这部分不清楚的可以阅读这里,一篇很清晰地讲述二分答案的题解。

代码

#include<iostream>
#include<algorithm>
using namespace std;
int t,m,n,r=0,l,mid,ans;
int a[100005];
bool check(int mid){
	int b = 1 ,j = 0;
	for(int i=1;i<n;i++){
		if(a[i]-a[j]>=mid){//两个棚之间的距离比最大距离mid还要大,此时可以再放进去一头牛
			b++;
			j=i;//更新j的坐标
		}		
	}
	return b>=m;
}
int main()
{
	cin>>n>>m;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	sort(a,a+n);//排序,排序,排序!!! 
	r=a[n-1]-a[0]; //棚的总长度

	while(l <= r){
	     mid=l+(r-l)/2;
		if(check(mid)){
			l = mid + 1;
		}
		else
		r=mid-1;
	}
	cout<< r <<endl;
	return 0;
}

前缀和与差分

详细内容见——>>前缀和 、差分

1、 [Poetize6] IncDec Sequence

题目描述

给定一个长度为 n n n 的数列 a 1 , a 2 , ⋯   , a n {a_1,a_2,\cdots,a_n} a1,a2,,an,每次可以选择一个区间 [ l , r ] [l,r] [l,r],使这个区间内的数都加 1 1 1 或者都减 1 1 1

请问至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列有多少种。

输入格式

第一行一个正整数 n n n
接下来 n n n 行,每行一个整数,第 $i+1 $行的整数表示 a i a_i ai

输出格式

第一行输出最少操作次数
第二行输出最终能得到多少种结果

样例 #1

样例输入 #1

4
1
1
2
2

样例输出 #1

1
2

提示

对于 100 % 100\% 100% 的数据, n ≤ 100000 , 0 ≤ a i ≤ 2 31 n\le 100000, 0 \le a_i \le 2^{31} n100000,0ai231

分析

(本篇题解我们的数组的下标从1开始)差分的概念是b[1]=a[1],b[i]=a[i]-a[i-1],

简单来说就是两个数的差。b[1]一定是等于a[1]的,因为b[1]=a[1]-a[0],

a[0]=0,所以b[1]=a[1]。

了解了概念,我们看一下差分和原序列之间有什么关系

  1. 把序列a的区间[l,r]+d(或者说a[l]+d,a[l+1]+d,a[l+2]+d+....+a[r]+d的话,那么这个序列a的差分序列b的变化就为b[l]+d,b[r+1]-d。为什么呢?举个例子

原序列a:1 3 4 2 1,其差分序列b:1 2 1 -2 -1

把区间[2,4]+2,得到的序列a应该是1 5 6 4 1

  1. 再看差分序列b,根据我们上面说的公式,a[2,4]+2应该等于b[2]+2,b[5]-2;

差分序列b变为:1 4 1 -2 -3

到底是不是这样呢?我们根据差分的概念倒推回去看看

由于b[1]=a[1],且b[1]=1,所以a[1]=1;

b[2]=4,则a[2]-a[1]=b[2]=4;由于a[1]=1,得出a[2]=5;

b[3]=1,则a[3]-a[2]=1,由于a[2]=5,得出a[3]=6;

b[4]=-2,则a[4]-a[3]=-2,由于a[3]=6,得出a[4]=4;

b[5]=-3,则a[5]-a[4]=-3,由于a[4]=4,得出a[5]=1;

由差分序列倒推回来得到的原序列是1 5 6 4 1,完全符合我们之前得到的,说明这个公式是正确的。

直观点说,原序列1 3 4 2 1,把区间[2,4]+2,得到的序列a是1 5 6 4 1

可以发现,a[2,4]中的差是不变的,因为他们同时加了一个数,变化的是a[l-1]和a[l]之间的差以及a[r]和a[r+1]之间的差,这样一来,就很好推出这个差分序列公式
下面是这个公式的延伸

如果a[l,r]+1,则b[l]+1,b[r+1]-1;

如果a[l,r]-1,则b[l]-1,b[r+1]+1;

如果a[l,n]+1(l <= n - 1),则b[l]+1,其余不变,因为b[n+1]已越界无意义

如果a[l,n]-1(l <= n - 1),则b[l]-1,其余不变,因为b[n+1]已越界无意义

下面看一下这个题该怎么做

  1. 要使得序列的数全部相等,其实就是让他们之间的差全为0,也就是差分序列的除了第一项每一项都是0,为什么除了第一项呢,因为b[1]=a[1]-a[0],而a[1]是开头的数
  2. 我们把问题转化成了让差分序列除第一项全等于0之后,继续思考
  3. 由于题目要求最少的步骤,我们可以考虑,如果差分序列里有一个正数和一个负数(出现的顺序无所谓),那么我们优先对这个正数和负数进行操作,为什么呢?因为我们有以下两个公式
如果a[l,r]+1,则b[l]+1,b[r+1]-1

如果a[l,r]-1,则b[l]-1,b[r+1]+1

正数-1,负数+1,这样相当于一步里作用了两步,比让正数一个个-1和让负数一个个+1快多了

  1. 那么我们可以进行多少种这样的操作呢?
  2. 我们可以令差分序列里正数绝对值的总和为p,负数绝对值总和为q
  3. 可以进行这样一步顶两步的操作就是min(p,q),因为这种操作正数负数是一一配对的,当少的那个先用完了,剩下的没有可以配对的了,只能一步步减或一步步加。
  4. 所以我们总共要进行的操作就为min(p,q)+abs(p-q),也就是max(p,q)

第一问完成,看第二问

保证最少次数的前提下,最终得到的数列有多少种?得到的数列有多少种,其实就是问的b[1]可以有多少种

我们上述所有操作是与b[1]无关的,因为我们的目标是让除了b[1]以外的项变0,所

以我们上述的操作没有考虑到b[1],b[1]怎么变,与我们求出的最小步骤无关

那么,我们怎么知道b[1]有几种呢?很简单,其实就是看看有几种一步步减或一步步

加的操作数,因为我们一步步加的时候(假设我们现在的操作对象下标为i),

可以这样操作,b[1]-1,b[i]+1

一步步减的时候可以这样操作,b[1]+1,b[i]-1

(注意,一个差分序列里一步步操作的时候只可能一步步加或一步步减,不可能一步步加和一步步减同时存在)

所以说,有几步一步步的操作就有几种情况**+1,**为什么+1呢,因为这个b[1]本身就有一个值啊!就算你不对他进行任何操作,它自己也有一种情况。

一加一减(也就是我们所说的一步顶两步的操作)操作数为min(p,q)

那么一步步的操作数就为max(p,q)-min(p,q)=abs(p,q)

代码

#include<iostream>


using namespace std;
typedef long long ll;
ll a[100010]={0},b[100010];
ll n,i,h,zheng=0,m,fu=0;
ll minn,fin;
int main()
{
	ll n;
	cin>>n;
	for(ll i=1;i<=n;i++){
		cin>>a[i];	    	
	}
	for(ll i=2;i<=n;i++){
		b[i]=a[i]-a[i-1];//差分
		if(b[i]>=0) zheng += b[i];//正数的次数
		else fu -= b[i];//负数的次数
	}
	minn=max(zheng,fu);
	fin=abs(zheng-fu)+1;
	cout<<minn<<endl<<fin;

	return 0;
 } 

2、 海底高铁

题目描述

该铁路经过 N N N 个城市,每个城市都有一个站。不过,由于各个城市之间不能协调好,于是乘车每经过两个相邻的城市之间(方向不限),必须单独购买这一小段的车票。第 i i i 段铁路连接了城市 i i i 和城市 i + 1 ( 1 ≤ i < N ) i+1(1\leq i<N) i+1(1i<N)。如果搭乘的比较远,需要购买多张车票。第 i i i 段铁路购买纸质单程票需要 A i A_i Ai 博艾元。

虽然一些事情没有协调好,各段铁路公司也为了方便乘客,推出了 IC 卡。对于第 i i i 段铁路,需要花 C i C_i Ci 博艾元的工本费购买一张 IC 卡,然后乘坐这段铁路一次就只要扣 B i ( B i < A i ) B_i(B_i<A_i) Bi(Bi<Ai) 元。IC 卡可以提前购买,有钱就可以从网上买得到,而不需要亲自去对应的城市购买。工本费不能退,也不能购买车票。每张卡都可以充值任意数额。对于第 i i i 段铁路的 IC 卡,无法乘坐别的铁路的车。

Uim 现在需要出差,要去 M M M 个城市,从城市 P 1 P_1 P1 出发分别按照 P 1 , P 2 , P 3 , ⋯   , P M P_1,P_2,P_3,\cdots,P_M P1,P2,P3,,PM 的顺序访问各个城市,可能会多次访问一个城市,且相邻访问的城市位置不一定相邻,而且不会是同一个城市。

现在他希望知道,出差结束后,至少会花掉多少的钱,包括购买纸质车票、买卡和充值的总费用。

输入格式

第一行两个整数, N , M N,M N,M

接下来一行, M M M 个数字,表示 P i P_i Pi

接下来 N − 1 N-1 N1 行,表示第 i i i 段铁路的 A i , B i , C i A_i,B_i,C_i Ai,Bi,Ci

输出格式

一个整数,表示最少花费

样例 #1

样例输入 #1

9 10
3 1 4 1 5 9 2 6 5 3
200 100 50
300 299 100
500 200 500
345 234 123
100 50 100
600 100 1
450 400 80
2 1 10

样例输出 #1

6394

提示

2 2 2 3 3 3 以及 8 8 8 9 9 9 买票,其余买卡。

对于 30 % 30\% 30% 数据 M = 2 M=2 M=2

对于另外 30 % 30\% 30% 数据 N ≤ 1000 , M ≤ 1000 N\leq1000,M\leq1000 N1000M1000

对于 100 % 100\% 100% 的数据 M , N ≤ 1 0 5 , A i , B i , C i ≤ 1 0 5 M,N\leq 10^5,A_i,B_i,C_i\le10^5 M,N105Ai,Bi,Ci105

分析

将每一段路程的开头+1,结尾-1,注意前后两站的顺序并不是按照大小排列的,所以需要判断一下大小,然后累加即可求出每一项的前缀和,这样只要跑一遍m跑一遍n即可,时间上优化了太多了

代码

#include<iostream>


using namespace std;
typedef long long ll;//前缀和必须用long long,不然最后三个点会WA
ll a[100010],b[100010],c[100010],s[100010],ans[100010];
ll n,i,h,l=0,m,r=0,sum=0;
ll minn,maxx;
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>s[i];
	}
	for(int i=1;i<n;i++) {
		cin>>a[i]>>b[i]>>c[i];
	}
	for(int i=1;i<m;i++){//对于每一段路程,开头+1,结尾-1标记一下
		ans[min(s[i],s[i+1])] ++;
		ans[max(s[i],s[i+1])] --;
		
	}
	for(int i=1;i<=n;i++){//然后累加求出每一项的前缀和 
		ans[i] +=ans[i-1];
	}
	for(i=1;i<n;i++)//判断买票和办卡哪个便宜,算出总花费  
	sum+=min(a[i]*ans[i],(b[i]*ans[i]+c[i]));	
    cout<<sum<<endl;//输出总价 
	return 0;
 } 

3、HDU 6273 Master of GCD

题意:

给出T组数据(1 <= T <= 10),每组数据中,有两个数n(1 <= n <= 10^5)和 m (1 <= m <= 10^5)。其中 n 表示有n个由1组成的数, m表示下面给出m组数据,每组数据由 p q k 组成。表示区间p 到 q,增大k倍。(k 等于2 或者 3).输出这n个数最终的最大公约数。由于数据比较大,因此需要mod 998244353。

测试数据

/*
2
5 3
1 3 2
3 5 2
1 5 3
6 3
1 2 2
5 6 2
1 6 2
*/

分析

这道题其实可以根据区间端点来查找。通过分别统计每个1~n个位置中,2的个数以及3的个数。(考虑到区间问题,所以这里需要注意两种情况 2~5, 和 6 ~ 7)这两种情况,一种数正好在区间内,另一种,正好再区间外,为了把1~n全部的联系在一起,所以在统计2和3 的个数的时候,只是将第一个端点个统计的,最后一个端点的下一个数先提前减1,这样在相加的时候就不会出现多加的情况。
最后通过找出2出现的最少次数和3出现的最小次数,求出它们的乘积,就是1~n之间最大公约数。

代码

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

typedef long long ll;//前缀和必须用long long
ll a[100010],b[100010];//乘2 or 乘3 分别存放 
ll n,i,h,l,m,r,x,t;
ll minn,minn1,sum,ans;
const ll mod=998244353;
ll fastpow(ll a,ll b){
	ll sum=1;
	 while(b){ 
        if(b & 1) { //此处等价于 if( b % 2 == 1)
		//b -= 1;    //如果指数是奇数的话有个减一操作,但在代码中我们可以省略这个,因为代码中指数是整数int类型,当power是奇数时小数点会被舍弃,这相当于power减一后再除二的操作。例如:power= 3的时候power / 2 = 相当于power-1后power=2后power / 2 = 1。所以代码可以写成这样。
		sum *= a; 
 }
		b>>=1;  //此处等价于 b = b / 2
        a = a * a % mod;
    }
    return sum;
}
int main()
{
	cin>>t;
	while(t--){
	    cin>>n>>m;
	    memset(a,0,sizeof(a));
	    memset(b,0,sizeof(b));
    	for(int i=1;i<=m;i++){
    		cin>>l>>r>>x;
    		if(x==2){
    			a[l]++;
    			a[r+1]--;
			}
			else{
				b[l]++;
				b[r+1]--;				
			}
		}
    			
		ll minn=1e20,minn1=1e20;
		for(int i=1;i<=n;i++){
			a[i]+=a[i-1];
			b[i]+=b[i-1];
			minn=min(minn,a[i]);
			minn1=min(minn1,b[i]);
		}
		sum=fastpow(2,minn)%mod;
		ans=sum%mod*fastpow(3,minn1)%mod;
	printf("%lld\n",ans);
  }
	return 0;
 } 

快速幂

快速幂

4、POJ-3263:Tallest Cow

FJ’s N (1 ≤ N ≤ 10,000) cows conveniently indexed 1…N are standing in a line. Each cow has a positive integer height (which is a bit of secret). You are told only the height H (1 ≤ H ≤ 1,000,000) of the tallest cow along with the index I of that cow.

FJ has made a list of R (0 ≤ R ≤ 10,000) lines of the form “cow 17 sees cow 34”. This means that cow 34 is at least as tall as cow 17, and that every cow between 17 and 34 has a height that is strictly smaller than that of cow 17.

For each cow from 1…N, determine its maximum possible height, such that all of the information given is still correct. It is guaranteed that it is possible to satisfy all the constraints.

Input
Line 1: Four space-separated integers: N, I, H and R
Lines 2… R+1: Two distinct space-separated integers A and B (1 ≤ A, B ≤ N), indicating that cow A can see cow B.
Output
Lines 1… N: Line i contains the maximum possible height of cow i.

Sample Input

9 3 5 5
1 3
5 3
4 3
3 7
9 8

Sample Output

5
4
5
3
4
4
5
5
5

概译:

N头牛排成一排,只知道最高的牛的下标I和它的高度H,然后给出R行,每行的A、B表示A可以看到B。而A可以看到B的定义为:B至少和A等高且A到B之间的牛全都严格比A矮。输出各个牛最大可能身高。

分析

因为数据一定合法所以可以朴素地先都假设等于最高,然后每次输入A和B都把A+1~B-1之间的牛的高度都减一。但复杂度O(NR)较大,可以用前缀和的思想优化,即: 把对一个区间的操作转化为左右两个端点上的操作。此题可以开一个额外的数组d,每输入A、B后,在d上的A+1下标处“–”意味着从此处开始要变矮,B下标处“++”意味着在此处结束,而d数组直接负责的是c数组,c数组代表的是第i头牛和第I头牛的身高差距,即c[I]一定等于0.

代码

#include<iostream>
#include<set>
//因为我们发现,题目中要求牛的身高最高,那么既然如此,我们完全可以将每一组关系(A,B),看作[A+1,B-1]这组牛身高只比A,B这两头牛矮1.
using namespace std;
set< pair<int,int> > se;//判断关系对是否存在重复

int a[10005]={0},d[10005];
int n,i,h,r,m,l;
int main()
{
	cin>>n>>i>>h>>m;
	d[1]=h;
	for(int i = 1;i <= m;i++){
		cin>>l>>r;
		if(l > r){
			swap(l , r);保证 l 比 r 小,即(l , r)是有效的
		}
		if(!se.count({l,r})){
			se.insert({l,r});
			d[l+1]--;
			d[r]++;//处理d[l + 1 ~ r - 1]的牛
		}
	}
	for(int i=1;i<=n;i++){
		a[i]=a[i-1]+d[i];
		cout<<a[i]<<endl;
	}
	
	return 0;
 } 

倍增法与ST算法点击查看详细知识点

ST图解

[USACO07JAN] Balanced Lineup G

题目描述

For the daily milking, Farmer John’s N cows (1 ≤ N ≤ 50,000) always line up in the same order. One day Farmer John decides to organize a game of Ultimate Frisbee with some of the cows. To keep things simple, he will take a contiguous range of cows from the milking lineup to play the game. However, for all the cows to have fun they should not differ too much in height.

Farmer John has made a list of Q (1 ≤ Q ≤ 180,000) potential groups of cows and their heights (1 ≤ height ≤ 1,000,000). For each group, he wants your help to determine the difference in height between the shortest and the tallest cow in the group.

每天,农夫 John 的 n ( 1 ≤ n ≤ 5 × 1 0 4 ) n(1\le n\le 5\times 10^4) n(1n5×104) 头牛总是按同一序列排队。

有一天, John 决定让一些牛们玩一场飞盘比赛。他准备找一群在队列中位置连续的牛来进行比赛。但是为了避免水平悬殊,牛的身高不应该相差太大。John 准备了 q ( 1 ≤ q ≤ 1.8 × 1 0 5 ) q(1\le q\le 1.8\times10^5) q(1q1.8×105) 个可能的牛的选择和所有牛的身高 h i ( 1 ≤ h i ≤ 1 0 6 , 1 ≤ i ≤ n ) h_i(1\le h_i\le 10^6,1\le i\le n) hi(1hi106,1in)。他想知道每一组里面最高和最低的牛的身高差。

输入格式

Line 1: Two space-separated integers, N and Q.

Lines 2…N+1: Line i+1 contains a single integer that is the height of cow i

Lines N+2…N+Q+1: Two integers A and B (1 ≤ A ≤ B ≤ N), representing the range of cows from A to B inclusive.

第一行两个数 n , q n,q n,q

接下来 n n n 行,每行一个数 h i h_i hi

再接下来 q q q 行,每行两个整数 a a a b b b,表示询问第 a a a 头牛到第 b b b 头牛里的最高和最低的牛的身高差。

输出格式

Lines 1…Q: Each line contains a single integer that is a response to a reply and indicates the difference in height between the tallest and shortest cow in the range.

输出共 q q q 行,对于每一组询问,输出每一组中最高和最低的牛的身高差。

样例 #1

样例输入 #1

6 3
1
7
3
4
2
5
1 5
4 6
2 2

样例输出 #1

6
3
0

分析

题意
求一个区间内的最大值减掉最小值,RMQ指的是求一个区间的某个数值(例如:最大值,最小值…)

预处理
设a[i]是要求区间最值的数列,F[i,j]表示从第i个数起连续2^j个数中的最大值。(DP的状态)

例如: a数列为:3 2 4 5 6 8 1 2 9 7

F[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数。

同理 F[1,1]=max(3,2)=3,F[1,2]=max(3,2,4,5) = 5 F[1,3] = max(3,2,4,5,6,8,1,2) = 8;

并且我们可以容易的看出F[i,0]就等于a[i]。(DP的初始值)

我们把F[i,j]平均分成两段(因为F[i,j]一定是偶数个数字), 从 i 到i + 2 ^ (j - 1) - 1为一段,i + 2 ^ (j - 1)到i + 2 ^ j - 1为一段(长度都为2 ^ (j - 1))。

于是我们得到了状态转移方程

F[i,j]=max( F[ i] [ j-1] ,F[i + ( 1<<(j-1) )][j-1 ]);

查询
假如我们需要查询的区间为(i,j),

那么我们需要找到覆盖这个闭区间(左边界取i,右边界取j) 的最小幂(可以重复,比如查询1,2,3,4,5,我们可以查询1234和2345)。

因为这个区间的长度为j - i + 1,所以我们可以取 k=log( j - i + 1)(以2为底)

则有:RMQ(i, j)=max{F[i , k], F[ j - 2 ^ k + 1, k]}。

代码

#include<iostream>
#include<algorithm> 
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+5;
int n,q,h[N],a,b,s[N][25],t[N][25];//用两个数组来分别计算最大值和最小值 
int l,r,x,y;
int main()
{
	cin>>n>>q;
	for(int i=1;i<=n;i++) {
		cin>>h[i];
		s[i][0]=h[i];//将身高存入数组 
		t[i][0]=h[i];
		
	}
	for(int j=1;j <= 20;j++){//这里的k我随便取到个最大值到20,只要2^k能超过题目的数据范围就行
		for(int i=1; i+(1<<j)-1<=n; i++){
			s[i][j] = max(s[i][j-1],s[i+(1 << (j-1) )][j-1]);//预处理,先把每一种可能都存入数组中,下面需要的时候直接输出 
			t[i][j] = min(t[i][j-1],t[i+(1 << (j-1) )][j-1]);
		}
	}
	while(q--){
		cin>>l>>r;
		int k=log2(r-l+1);// 2^k 为区间长度 
		//已知左右边界,就可以从两头循环到中间,(1《 k)相当于 2^k  
		x=max(s[l][k],s[r-(1<<k)+1][k]);
		y=min(t[l][k],t[r-(1<<k)+1][k]);
		 printf("%d\n",x-y);
	}
	return 0;
}

# 【模板】ST 表

题目背景

这是一道 ST 表经典题——静态区间最大值

请注意最大数据时限只有 0.8s,数据强度不低,请务必保证你的每次查询复杂度为 O ( 1 ) O(1) O(1)。若使用更高时间复杂度算法不保证能通过。

如果您认为您的代码时间复杂度正确但是 TLE,可以尝试使用快速读入:

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

函数返回值为读入的第一个整数。

快速读入作用仅为加快读入,并非强制使用。

题目描述

给定一个长度为 N N N 的数列,和 $ M $ 次询问,求出每一次询问的区间内数字的最大值。

输入格式

第一行包含两个整数 N , M N,M N,M,分别表示数列的长度和询问的个数。

第二行包含 N N N 个整数(记为 a i a_i ai),依次表示数列的第 i i i 项。

接下来 M M M 行,每行包含两个整数 l i , r i l_i,r_i li,ri,表示查询的区间为 [ l i , r i ] [l_i,r_i] [li,ri]

输出格式

输出包含 M M M 行,每行一个整数,依次表示每一次询问的结果。

样例 #1

样例输入 #1

8 8
9 3 1 7 5 6 0 8
1 6
1 5
2 7
2 6
1 8
4 8
3 7
1 8

样例输出 #1

9
9
7
7
9
8
7
9

提示

对于 30 % 30\% 30% 的数据,满足 1 ≤ N , M ≤ 10 1\le N,M\le 10 1N,M10

对于 70 % 70\% 70% 的数据,满足 1 ≤ N , M ≤ 10 5 1\le N,M\le {10}^5 1N,M105

对于 100 % 100\% 100% 的数据,满足 1 ≤ N ≤ 10 5 1\le N\le {10}^5 1N105 1 ≤ M ≤ 2 × 10 6 1\le M\le 2\times{10}^6 1M2×106 a i ∈ [ 0 , 10 9 ] a_i\in[0,{10}^9] ai[0,109] 1 ≤ l i ≤ r i ≤ N 1\le l_i\le r_i\le N 1liriN

分析

求区间内的最大值

代码

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int MAXN=1e6+10;

inline int read()
{
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
int a[MAXN][21];
int Query(int l,int r)
{
    int k=log2(r-l+1); 
    return max(a[l][k],a[r-(1<<k)+1][k]);//把拆出来的区间分别取最值 
}
int main()
{
    
    int N=read(),M=read();
    for(int i=1;i<=N;i++) a[i][0]=read();
    for(int j=1;j<=21;j++)
        for(int i=1;i+(1<<j)-1<=N;i++)//注意这里要控制边界 
            a[i][j]=max(a[i][j-1],a[i+(1<<(j-1))][j-1]);//如果看不懂边界的话建议好好看看图 
    for(int i=1;i<=M;i++)
    {
        int l=read(),r=read();
        printf("%d\n",Query(l,r));
    }
    return 0;
}

排序与排列,分治法 [点击看详细知识点]

# 最大子段和

题目描述

给出一个长度为 n n n 的序列 a a a,选出其中连续且非空的一段使得这段和最大。

输入格式

第一行是一个整数,表示序列的长度 n n n

第二行有 n n n 个整数,第 i i i 个整数表示序列的第 i i i 个数字 a i a_i ai

输出格式

输出一行一个整数表示答案。

样例 #1

样例输入 #1

7
2 -4 3 -1 2 -4 3

样例输出 #1

4

提示

样例 1 解释

选取 [ 3 , 5 ] [3, 5] [3,5] 子段 { 3 , − 1 , 2 } \{3, -1, 2\} {3,1,2},其和为 4 4 4

数据规模与约定
  • 对于 40 % 40\% 40% 的数据,保证 n ≤ 2 × 1 0 3 n \leq 2 \times 10^3 n2×103
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 2 × 1 0 5 1 \leq n \leq 2 \times 10^5 1n2×105 − 1 0 4 ≤ a i ≤ 1 0 4 -10^4 \leq a_i \leq 10^4 104ai104

分析

枚举 l,r,求所有区间的和然后取最大值的时间开销很大,不能通过本题,我们来思考更优秀的做法。

首先,我们现在纸上手算一下样例是怎么来的:

2 -4 3 -1 2 -4 3
ans:4

可以发现选 3 -1 2 是一种合法的方案。那么是怎么推出来的呢?

首先看到第一个数,是 2。而 2 后面是 -4,所以如果 -4 是答案的一部分,那么 2 一定也要加上去(这样答案就增加了,会比原来优)。

随后是 3。如果 3 把前面的 2 和 -4加上去,结果是 1。这个时候反而比原来的单独一个 3 要小。所以如果答案含有 3,就一定不会加上前面的 2 和 -4(加上前面的部分答案变小,不如到这里为止)。

下一个数是 -1。这个数加上前面的 3 之后答案增加了(变成了 2),所以如果答案有 -1,那么绝对还有前面的 3。
接下来是 2,如果 2 加上前面的序列 (3,-1),辣么它的值变为 4。比原先增加了。然后是 -4,如果把 -4 加上前面的序列 (3,-1,2),结果会变成 0,比原先的 -4 大,所以如果 -4 是答案的一部分,那么前面的三个数也一定是答案的一部分。最后一个数 3,如果将 3 加上前面的序列,结果变成了 3,没有变,所以这个可加可不加。
最后我们来看一看刚推导的结果,发现 4 是我们可以得出的最大和。

所以说了这么多,最终的结果是什么呢?

第一个数为一个有效序列

  1. 如果一个数加上上一个有效序列得到的结果比这个数大,那么该数也属于这个有效序列。
  2. 如果一个数加上上一个有效序列得到的结果比这个数小,那么这个数单独成为一个新的有效序列
  3. 在执行上述处理的过程中实时更新当前有效序列的所有元素之和并取最大值。

然后就可能有人问了:考虑上面样例推导中,出现了一个可加可不加的 3。如何处理?

结论是:对于可加可不加的数,不如加上。因为加上对答案没有坏处,而如果这个数后面还有一部分能让答案变多,因为本题求的子段是连续子段,不加上的话这两边就连不起来了。所以无脑加就行了。

最后取最大值即可。

代码

#include <iostream>
#include<algorithm>
using namespace std;
const int N=2e5+5;
int a[N],sum[N];
int ans=-2147483647;
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];

		sum[i] = max(sum[i-1]+a[i],a[i]);
		ans=max(sum[i],ans);
	}
	cout<<ans;  
    return 0;
}

逆序对

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i > a j a_i>a_j ai>aj i < j i<j i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

Update:数据已加强。

输入格式

第一行,一个数 n n n,表示序列中有 n n n个数。

第二行 n n n 个数,表示给定的序列。序列中每个数字不超过 1 0 9 10^9 109

输出格式

输出序列中逆序对的数目。

样例 #1

样例输入 #1

6
5 4 2 6 3 1

样例输出 #1

11

提示

对于 25 % 25\% 25% 的数据, n ≤ 2500 n \leq 2500 n2500

对于 50 % 50\% 50% 的数据, n ≤ 4 × 1 0 4 n \leq 4 \times 10^4 n4×104

对于所有数据, n ≤ 5 × 1 0 5 n \leq 5 \times 10^5 n5×105

请使用较快的输入输出

应该不会 O ( n 2 ) O(n^2) O(n2) 过 50 万吧 by chen_zhe

分析

归并排序是建立在归并操作上的一种有效的排序算法,该算法采用的是分治法(二分法)。

代码

#include <iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=5e5+5;
ll a[N];//原数组
ll b[N];//临时数组
ll sum;
ll mid,l,r;

void merge(ll l, ll mid, ll r){//合并函数
	ll i=l , j=mid+1, t=l;i、j指向需要合并的两个序列的首位置
	while(i <= mid && j <= r){//i、j指向需要合并的两个序列的首位置
		if(a[i]>a[j]){//将较小数存储在temp之中,并且后移指针
			
			b[t++]=a[j++];
			
			sum += mid - i + 1;
		}
		else b[t++] = a[i++];
	}
	while(i<=mid) b[t++]=a[i++];//左半序列还有元素
	while(j<=r) b[t++] = a[j++];//右半序列还有元素
	for(int i=l;i<=r;i++){//拷贝元素
		a[i] = b[i];
	}
}
void mergesort(ll l,ll r){//递归设计:排序函数
	if(l<r){
		ll mid = (l+r)/2;
		mergesort(l,mid);//递归设计:排序函数
		mergesort(mid+1,r);//归并排序右半序列
		merge(l,mid,r);//将左右合并
	}
}
int main()
{
	ll n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		
	}
	mergesort(1,n);//对第1个位置到第n个位置进行归并排序
	cout<<sum;  
    return 0;
}

# [NOIP1998 普及组] 幂次方

题目描述

任何一个正整数都可以用 2 2 2 的幂次方表示。例如 $137=27+23+2^0 $。

同时约定方次用括号来表示,即 a b a^b ab 可表示为 a ( b ) a(b) a(b)

由此可知, 137 137 137 可表示为 2 ( 7 ) + 2 ( 3 ) + 2 ( 0 ) 2(7)+2(3)+2(0) 2(7)+2(3)+2(0)

进一步:

7 = 2 2 + 2 + 2 0 7= 2^2+2+2^0 7=22+2+20 ( 2 1 2^1 21 2 2 2 表示),并且 3 = 2 + 2 0 3=2+2^0 3=2+20

所以最后 137 137 137 可表示为 2 ( 2 ( 2 ) + 2 + 2 ( 0 ) ) + 2 ( 2 + 2 ( 0 ) ) + 2 ( 0 ) 2(2(2)+2+2(0))+2(2+2(0))+2(0) 2(2(2)+2+2(0))+2(2+2(0))+2(0)

又如 1315 = 2 10 + 2 8 + 2 5 + 2 + 1 1315=2^{10} +2^8 +2^5 +2+1 1315=210+28+25+2+1

所以 1315 1315 1315 最后可表示为 2 ( 2 ( 2 + 2 ( 0 ) ) + 2 ) + 2 ( 2 ( 2 + 2 ( 0 ) ) ) + 2 ( 2 ( 2 ) + 2 ( 0 ) ) + 2 + 2 ( 0 ) 2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0) 2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)

输入格式

一行一个正整数 n n n

输出格式

符合约定的 n n n 0 , 2 0, 2 0,2 表示(在表示中不能有空格)。

样例 #1

样例输入 #1

1315

样例输出 #1

2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)

提示

【数据范围】

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 2 × 10 4 1 \le n \le 2 \times {10}^4 1n2×104

分析

主要思路是递归/分治,因为分解出的指数还要继续分解,是重复的但规模更小的问题。暴力枚举即可,数据很小

代码

#include<iostream>
#include<cmath>
using namespace std;
int a;
void fff(int x)
{
    for(int i=14;i>=0;i--) //两万的数据最多是2(14)
    {
        if(pow(2,i)<=x){
        //pow(n,m)在cmath库中,返回n^m;枚举出第一个幂次方
            if(i==1) cout<<"2"; //2(1)不用再往后分解了且2^1输出为2,单独出来
            else if(i==0) cout<<"2(0)"; //2(0)也不用再往后分解了,单独出来
            else{ //若i>1则继续分解指数i
                cout<<"2(";
            fff(i);
            cout<<")";
            }
            x-=pow(2,i); //继续循环分解余下的
            if(x!=0) cout<<"+";
            //加号处理的最简单方法:若此x还没分解完,则后面还有项,所以输出一个+号
        }
    }
}
int main()
{
    cin>>a;
    fff(a);
    return 0;
}

B - Fractal

题目描述

分形,具有以非整数维形式充填空间的形态特征。通常被定义为“一个粗糙或零碎的几何形状,可以分成数个部分,且每一部分都(至少近似地)是整体缩小后的形状”,即具有自相似的性质。

现在,定义“盒子分形”如下:

一级盒子分形:


   X
   

二级盒子分形:

   X X
    X
   X X

如果用 B(n−1) 代表第n−1 级盒子分形,那么第 n 级盒子分形即为:

  B(n - 1)        B(n - 1)
 
          B(n - 1)
 
  B(n - 1)        B(n - 1)

输入格式

输入包含几个测试用例。

输入的每一行包含一个不大于 7 的正整数 n,代表要输出的盒子分形的等级。

输入的最后一行为 −1,代表输入结束。

输出格式

对于每个测试用例,使用 X 符号输出对应等级的盒子分形。

请注意 X 是一个大写字母。

每个测试用例后输出一个独立一行的短划线。

输入样例:

1
2
3
4
-1

输出样例:

X
-
X X
 X
X X
-
X X   X X
 X     X
X X   X X
   X X
    X
   X X
X X   X X
 X     X
X X   X X
-
X X   X X         X X   X X
 X     X           X     X
X X   X X         X X   X X
   X X               X X
    X                 X
   X X               X X
X X   X X         X X   X X
 X     X           X     X
X X   X X         X X   X X
         X X   X X
          X     X
         X X   X X
            X X
             X
            X X
         X X   X X
          X     X
         X X   X X
X X   X X         X X   X X
 X     X           X     X
X X   X X         X X   X X
   X X               X X
    X                 X
   X X               X X
X X   X X         X X   X X
 X     X           X     X
X X   X X         X X   X X
-

分析:

找规律:
递归找规律,我们可以发现对于每个盒子都是正方形,边长都一样;

对于第n级的盒子分型,其边长为3^(n-1),例如2级盒子边长为3,3级盒子边长为9;

对于第n级盒子,总是由n-1级盒子复制到其他四个地方的;

n-1级盒子在左上角,n级盒子需要其复制到右上角,中间,左下角,右下角;

解法:

用一个二维数组表示整个7级盒子,如果某个位置该放’X’,就将其值置为1,否则为0;

先进行一次性预处理,求出7级盒子的样子,再根据输入的数值,输出对应大小的数组;

我们可以用len_n表示第n级盒子的边长,len_n_1表示第n-1级盒子的边长;

则我们可以发现,n级盒子右上角距离n-1级盒子的距离为2倍len_n_1,

等于将n-1级盒子向右平移2倍len_n_1的距离;

中间的部分等于n-1级盒子向下平移len_n_1,再向右平移len_n_1的距离;

左下角的部分等于n-1级盒子向下平移2倍len_n_1的距离;

右下角的部分等于n-1级盒子向右平移2倍len_n_1,向下平移2倍len_n_1的距离;

由此,递归的关系式就非常简单了,我们可以像一般的深搜一样,用两个一维数组表示行和列的变化(单位是len_n_1);

然后根据变化前的状态确定变化后的状态;(由于是复制过来的,所以变化前后状态一样);

最后根据输入的数字,输出对应边长大小的矩阵;

代码

#include <iostream>
#include<cmath>
#include<algorithm>
#include<cstring>

using namespace std;
char map[1000][1000];
void paint(int n,int x,int y){
 if(n==1){
  map[x][y]='X';
  return;
 }
 int size = (int)pow(3.0,n-2);    
 paint(n-1, x, y);      //左上角
 paint(n-1, x, y+size*2);   //右上角
 paint(n-1, x+size, y+size);         //中間
 paint(n-1, x+size*2, y);            //左下角
 paint(n-1, x+size*2, y+size*2); //右下角
}
int main(){
	int n;
	while (~scanf("%d",&n)&&n!=-1){
	  int size=(int)pow(3.0, n-1);       //度为n的分形图的规模是3^(n-1)
	  memset(map,' ',sizeof(map));
	  paint(n, 1, 1);
	  for (int i=1;i<=size;i++ )   {   //列印         
	    for(int j=1;j<=size;j++)
	     cout<<map[i][j];
	    cout<<endl;         
	  }
	   cout<<'-'<<endl;
	}
 return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值