集训第五周第六周

一. 并查集

并查集总共有两种操作:

1.查找

2.合并

基础版

int n;
vector<int> parent;
void Init(int n) {
    for (int i = 0; i < n; i++) {
        int a;
        cin >> a;
        parent.push_back(a);
    }
}
int find(int x) {//用来找祖先结点
    if (parent[x] == x) {
        return x;
    }
    else {
        return find(parent[x]);
    }
}
void merge(int x, int y) {
    //注意!想让y合并到x,要先找到x和y各自的祖先结点,再进行合并
    parent[find(y)] = find(x);;

}
signed main() {
    cin >> n;
    Init(n);
    
    
    return 0;
}

路径压缩

int find(int x) {
    if (parent[x] == x) {
        return x;
    }
    else {
        parent[x] = find(parent[x]);
        //边查找变修改,使当前结点直属于其祖先结点
        //也就是让祖先结点成为其父结点
        return parent[x];
    }
}

可简化为

int find(int x) {
    return parent[x] == x ? x : (parent[x] = find(parent[x]));
}

按秩合并

void merge(int x, int y) {
    //先查看哪边的深度更深,然后把浅的并到深的里面
    //注意!用于比较的深度是一整棵树的深度
    //所以要找到x和y的祖先结点,那里存着一整个树的深度

    int rx = find(x);
    int ry = find(y);

    if (rx != ry) {
        //先判断一下x和y的祖先结点是不是同一个
        //如果是同一个结点,当然就没必要操作了

        if (Rank[rx] < Rank[ry]) {
            swap(rx, ry);//让rx成为较深的那个
            //硬要用if else也行,但是不利于后面的操作
        }
        parent[ry] = rx;//ry并到rx里

        if (Rank[rx] == Rank[ry]) {
            //在合并前,两颗树的深度是一样的
            //在ry合并到rx后,rx的深度会+1
            Rank[rx] += 1;

        }
        
    }

}

通用模板

int n;
vector<int> parent;
vector<int> Rank;//在每个结点记录当前深度
void Init(int n) {
    for (int i = 0; i < n; i++) {
        int a;
        cin >> a;
        parent.push_back(a);
        Rank.push_back(1);//一开始自己是自己的老大,深度为1
    }
}
int find(int x) {
    return parent[x] == x ? x : (parent[x] = find(parent[x]));
}
void merge(int x, int y) {
    //先查看哪边的深度更深,然后把浅的并到深的里面
    //注意!用于比较的深度是一整棵树的深度
    //所以要找到x和y的祖先结点,那里存着一整个树的深度

    int rx = find(x);
    int ry = find(y);

    if (rx != ry) {
        //先判断一下x和y的祖先结点是不是同一个
        //如果是同一个结点,当然就没必要操作了

        if (Rank[rx] < Rank[ry]) {
            swap(rx, ry);//让rx成为较深的那个
            //硬要用if else也行,但是不利于后面的操作
        }
        parent[ry] = rx;//ry并到rx里

        if (Rank[rx] == Rank[ry]) {
            //在合并前,两颗树的深度是一样的
            //在ry合并到rx后,rx的深度会+1
            Rank[rx] += 1;

        }
        
    }

}
signed main() {
    cin >> n;
    Init(n);
    
    
    return 0;
}

题型归纳:找是否有公共祖先!没了!

1.修复公路

Sample 1

InputcopyOutputcopy
4 4
1 2 6
1 3 4
1 4 5
4 2 3
5

分析:

各个村庄都是独立不相关的点,我理解成村庄在一条线性的线段上,以为村庄1到村庄5修了路,村庄234都可以到5。

因此我们可以知道,想要任意村庄能通车,每个村庄间都至少得修一条路,比如说有n个村庄,就要修n-1条路。

然后输入数据的时候总想着查重,但其实有些题没必要(因为稍后的操作可以自动避开),直接结构体一股脑输入就行了。(我记得之前做过有一题也是这样)

我们求的是最短通行时间,这是由要修的n-1条路中,时间耗费最久的那条路决定(因为所有路都是同时开始修)

因为求的时间尽可能短,可以想到结构体按时间 t 排序(额总之没什么思路就想想要不要预排序,可能有惊喜)

接下来构建并查集,如果当前遍历到的祖先结点是之前没出现过的,才并入(这不就去重了吗),也意味着新路出现了,cnt++

解释一下

#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define int long long
//#include<bits/stdc++.h>
const int N = 1e6 + 10;
int n, m, k,cnt = 0;
int f[200000];
map<pair<int, int>, int> path;//默认初始化为0
int find(int x) { return f[x] == x ? x : (f[x] = find(f[x])); }

struct Node {
    int x, y, t;
}node[200000];
bool cmp(Node a, Node b) {
    return a.t < b.t;
}
signed main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        f[i] = i;
    }
    for (int i = 1; i <= m; i++) {
        cin >> node[i].x >> node[i].y >> node[i].t;
    }

    sort(node + 1, node + 1 + m,cmp);

    for (int i = 1; i <= m; i++) {
        int rx = find(node[i].x);
        int ry = find(node[i].y);
        if (rx != ry) {
            f[rx] = ry; 
            cnt++;
        }
        if (cnt==n-1) {
            cout << node[i].t;
            return 0;
        }

    }
    cout << -1;

    return 0;
}

2.一中校运会之百米跑

Sample 1

InputcopyOutputcopy
10 6
Jack
Mike
ASDA
Michel
brabrabra
HeHe
HeHE
papapa
HeY
Obama
Jack Obama
HeHe HeHE
brabrabra HeHe
Obama ASDA
papapa Obama
Obama HeHE
3
Mike Obama
HeHE Jack
papapa brabrabra
No.
Yes.
Yes.

基本上模板题,只是由int改成string。

需要注意的是,注意要想到可以用map,别用vector嵌套pair,不好用。

#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define int long long
//#include<bits/stdc++.h>
const int N = 1e6 + 10;
int n, m, k;
map<string, string> mp;
string find(string s) {
    if (mp[s] == s) return s;
    mp[s] = mp[find(mp[s])];//注意这里,别写成find(s)陷入死循环
    return mp[s];
}
void merge(string s1, string s2) {
    string x1 = find(s1);
    string x2 = find(s2);
    if (x1 != x2) {
        mp[x1] = x2;
    }
}
signed main() {
    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        string s;
        cin >> s;
        mp[s] = s;
    }

    for (int i = 0; i < m; i++) {
        string s1, s2;
        cin >> s1 >> s2;
        merge(s1, s2);
    }
     
    cin >> k;
    while (k--) {
        string s1, s2;
        cin >> s1 >> s2;
        if (find(s1) == find(s2)) cout << "Yes." << endl;
        else cout << "No." << endl;
    }
    return 0;
}

3.The Suspects

Sample

InputcopyOutputcopy
100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0
4
1
1

#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define int long long
//#include<bits/stdc++.h>
const int N = 1e6 + 10;
int n, m, k,cnt = 0;
int f[N];
int a[N];
int find(int x) {
    return f[x] == x ? x : (f[x] = find(f[x]));
}
void merge(int x, int y) {
    int rx = find(x), ry = find(y);
    if (rx != ry) {
        f[rx] = ry;
    }
}

signed main() {
    
    while (cin >> n >> m && (n != 0 || m != 0)) {
        int cnt = 0;//0本身就是
        memset(f, 0, sizeof(f));
        for (int i = 0; i < n; i++) {
            f[i] = i;
        }
        while (m--) {
            cin >> k;
            int x;
            cin >> x;
            for (int i = 1; i < k; i++) {
                int y;
                cin >> y;
                merge(x, y);
            }

        }

        for (int i = 0; i < n; i++) {
            if (find(i) == find(0)) {//这里需要注意,因为0可能被并到别的数下面
                cnt++;               //除非上面并数时,限定了把大的数并到小的数下面
            }
        }
        cout << cnt << endl;
    }
    return 0;
}

二. 线段树

基本模板(不用结构体版)

int a[] = { 1,3,5,7,9,11 };
int sz = 6;
int tree[N];
//以前学过,给定一个数组,将数组的数据排成大根堆或小根堆
//这个也类似,给个数组,构造一个线段树。但不同于堆,
//构造出来的线段树元素会比数组的元素多,而堆本质上只是将一组数据排序而已

void build_tree(int root, int start, int end) {
    //都是下标。root是树的根结点下标。start和end属于a数组的下标
    if (start == end) {
        //找到叶结点了
        tree[root] = a[start];
    }
    else {
        int mid = (start + end) / 2;
        int left_node = root * 2 + 1;
        int right_node = root * 2 + 2;

        build_tree(left_node, start, mid);
        build_tree(right_node, mid + 1, end);
        //排序左右子树。注意根结点的左孩子和右孩子的表达方式

        tree[root] = tree[left_node] + tree[right_node];

    }
}
void update_tree(int root, int start, int end, int index, int val) {
    //a[index]要被修改成val
    if (start == end) {
        //走到最底层了,也就是叶子结点
        a[index] = val;
        tree[root] = val;
    }
    else {
        int mid = (start + end) / 2;
        int left_node = root * 2 + 1;
        int right_node = root * 2 + 2;
        if (index >= start && index <= mid) {
            update_tree(left_node, start, mid, index, val);
        }
        else {
            update_tree(right_node, mid + 1, end, index, val);
        }

        tree[root] = tree[left_node] + tree[right_node];

    }
}
int query_tree(int root, int start, int end, int L, int R) {
    cout << "start:" << start << endl;
    cout << "end:" << end << endl << endl;

    if (R<start || L>end) {
        //没有交集
        return 0;
    }
    else if (start >= L && end <= R) {
        //完全被包含
        return tree[root];
    }
    else if (start==end) {
        //已经到叶节点了,且这个叶结点也在要求的范围内
        return tree[root];
    }
    else {
        //既不是叶结点,又有交集,又不是被包含
        //所以还要再细分
        int mid = (start + end) / 2;
        int left_node = root * 2 + 1;
        int right_node = root * 2 + 2;
        int sum_left = query_tree(left_node, start, mid, L, R);
        int sum_right = query_tree(right_node, mid + 1, end, L, R);
        return sum_left + sum_right;
    }

}
signed main() {
    //每次进行操作,都必须告知函数,树的根结点以及数组的开头结尾。为了递归能实现。
    build_tree(0, 0, sz - 1);
    update_tree(0, 0, sz - 1, 4, 6);
    //for (int i = 0; i < 15; i++) cout << tree[i] << endl;
    cout << query_tree(0, 0, sz - 1, 2, 5);
    return 0;
}

1.线段树

Sample 1

InputcopyOutputcopy
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
11
8
20

简单模板。涉及区间查询和区间修改和懒标记

#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define ll long long 
#define lson x << 1
#define rson x << 1 | 1
const int N = 1e5 + 10;
struct node {
	ll l, r, sum, lazy;
}tree[N << 2];
int a[N];

//void push_up()
void build(int x, int l, int r) {
	tree[x] = { l,r,0,0 };
	if (l == r) {
		tree[x].sum = a[l];
		return;
	}
	int mid = (l + r) / 2;
	build(lson, l, mid);
	build(rson, mid + 1, r);
	tree[x].sum = tree[lson].sum + tree[rson].sum;
}

void push_down(int x) {
	if (tree[x].lazy) {
		tree[lson].lazy += tree[x].lazy;
		tree[rson].lazy += tree[x].lazy;
		//如果传进来根节点的x区间是[4,7],说明4到7的区间下的子树都需要打上懒标记
		tree[lson].sum += tree[x].lazy * (tree[lson].r - tree[lson].l + 1);
		tree[rson].sum += tree[x].lazy * (tree[rson].r - tree[rson].l + 1);

		tree[x].lazy = 0;//懒标记已经传给下一层了,父节点清零
	}
}


void add(int x, int l, int r, int val) {
	//区间修改
	if (tree[x].l >= l && tree[x].r <= r) {
		tree[x].sum += val * (tree[x].r - tree[x].l + 1);
		tree[x].lazy += val;
		//只有完全覆盖时才将区间打上懒标记,
		//打上后不一定马上就将该区间下的子树都更新,
		//而是先标记,后续遍历到有懒标记的结点才将懒标记传给左右子树,
		//并更新左右子树的值。
		//同样的,左右子树也不一定会将懒标记一次性传到底层,
		//主要是看当前遍历到哪个结点,才把懒标记传到该节点的下一层

		return;
	}

	push_down(x);
	if (tree[lson].r >= l) add(lson, l, r, val);
	if (tree[rson].l <= r) add(rson, l, r, val);

	tree[x].sum = tree[lson].sum + tree[rson].sum;

}

//区间查询
ll query(int x, int l, int r) {
	if (tree[x].l >= l && tree[x].r <= r)
	{
		return tree[x].sum;
	}
	if (tree[x].l > r || tree[x].r < l) {
		return 0;
	}

	push_down(x);//为什么查询也要下沉
	ll sum = 0;
	if (tree[lson].r >= l) sum += query(lson, l, r);
	if (tree[rson].l <= r) sum += query(rson, l, r);

	return sum;
}
//void add_point(int x,int )
int main()
{
	

	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
	}

	build(1, 1, n);

	while (m--)
	{
		int flag, x, y, k;
		cin >> flag;
		if (flag == 1)
		{
			cin >> x >> y >> k;
			add(1, x, y, k);
		}
		else
		{
			cin >> x >> y;
			cout << query(1, x, y) << endl;
		}
	}

	return 0;
}

三. 树状数组

功能:

1.求前缀和 2.求区间和 3.单点修改 4.区间更新(可以但不好用,要用线段树)

有个特点:数组c的下标i,可以用来表示c[i]的区间长度。

 

模板

int a[N];
int c[N];

int lowerbit(int i) {
	//求c[i]的区间长度
	return (-i) & i;
}

int sum(int i) {
	//求前缀和,将各个直接前驱相加
	//怎么找到i的直接前驱呢?
	//因为c[i]的区间长度是lowerbit(i)
	//所以 i - lowerbit(i) 就是i的直接前驱
	int ans = 0;
	for (; i > 0; i -= lowerbit(i)) {
		ans += c[i];
	}
	return ans;
}
int query(int i, int j) {
	//求区间和
	return sum(j) - sum(i - 1);

}
void add(int i, int val) {
	//点更新,将i及其所有后驱都加上val
	for (; i <= n ; i += lowerbit(i)) {
		c[i] += val;
	}

}
signed main() {
	//建树
	for (int i = 1; i <= n; i++) {
		//注意数组c要从1开始
        cin>>a[i];
		add(i, a[i]);
	}

	return 0;
}

1. 逆序对

Sample 1

InputcopyOutputcopy
6
5 4 2 6 3 1
11

如果数字在1e6左右,直接这样做就行了

总结的来说,本质上是哈希表,就是先将树状数组初始化为0,代表所有数都还没出现过。

每出现了一个数x,就在hash[x]++。

那么逆序对怎么计算?

假设是从数组后面往前遍历(从前往后同理),遍历的下标是递减的,因此如果hash中有值为1,一定是在这之前出现过的(下标大于当前的)。这样就满足了其中一个逆序对的条件。然后再看看这个值是不是小于当前遍历的数,如果是,满足了逆序对的两个条件。

假设我们有一个数组 nums = [7, 5, 6, 4],现在我们要统计该数组中的逆序对个数。

首先,我们定义一个长度为 5 的树状数组 bit,初始化所有元素为 0:bit = [0, 0, 0, 0, 0]。这是因为数组 nums 的最大值是 7,所以树状数组的长度需要至少是 8(比最大值多 1)。

然后,我们从后向前遍历数组 nums

  1. 当处理到 nums[3] = 4 时:

    • 我们查询在树状数组 bit 中比 4 小的元素个数,即 query(4),由于此时 bit[4] = 0,表示在 4 之前没有比它小的元素。
    • 然后,我们将 4 加入树状数组 bit 中,即 update(4, 4)。这会将 bit[4] 的值自增 1,结果变为 1。
    • 继续下一个元素。
  2. 当处理到 nums[2] = 6 时:

    • 我们查询在树状数组 bit 中比 6 小的元素个数,即 query(6),由于此时 bit[6] = 1,表示在 6 之前有一个比它小的元素,即 4。
    • 然后,我们将 6 加入树状数组 bit 中,即 update(6, 4)。这会将 bit[6] 的值自增 1,结果变为 2。
    • 继续下一个元素。
  3. 当处理到 nums[1] = 5 时:

    • 我们查询在树状数组 bit 中比 5 小的元素个数,即 query(5),由于此时 bit[5] = 1,表示在 5 之前有一个比它小的元素,即 4。
    • 然后,我们将 5 加入树状数组 bit 中,即 update(5, 4)。这会将 bit[5] 的值自增 1,结果变为 2。
    • 继续下一个元素。
  4. 当处理到 nums[0] = 7 时:

    • 我们查询在树状数组 bit 中比 7 小的元素个数,即 query(7),由于此时 bit[7] = 2,表示在 7 之前有两个比它小的元素,分别是 4 和 5。
    • 然后,我们将 7 加入树状数组 bit 中,即 update(7, 4)。这会将 bit[7] 的值自增 1,结果变为 3。
    • 继续下一个元素。

完成整个遍历后,我们统计到树状数组 bit 的状态为 [0, 0, 0, 1, 2]。最后,我们将查询到的前缀和累加起来,得到逆序对的个数:0 + 0 + 0 + 1 + 2 = 3。

所以,数组 nums 中的逆序对个数为 3。

但是!序列中每个数字最大是1e9,不能开一个这么大的数组。

所以要离散化。

具体操作为先按val优先排升序,然后把val改成1-n。这样就可以开数组了(最大占空间1e5)

然后再按事先存的原下标一个个插入ranks数组。

有个排序的注意点

#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
//#define int long long
//#include<bits/stdc++.h>
const int N = 1e6 + 10;
int tree[500010], ranks[500010], n;
long long ans;
struct point
{
    int num, val;
}a[500010];
int c[N];
inline bool cmp(point q, point w)
{
    if (q.val == w.val)
        return q.num < w.num;
    return q.val < w.val;
}
int lowerbit(int i) {
    //求c[i]的区间长度
    return (-i) & i;
}
void add(int i, int val) {
    //点更新,将i及其所有后驱都加上val
    for (; i <= n; i += lowerbit(i)) {
        c[i] += val;
    }

}
int sum(int i) {
    //求前缀和,将各个直接前驱相加
    //怎么找到i的直接前驱呢?
    //因为c[i]的区间长度是lowerbit(i)
    //所以 i - lowerbit(i) 就是i的直接前驱
    int ans = 0;
    for (; i > 0; i -= lowerbit(i)) {
        ans += c[i];
    }
    return ans;
}
int query(int i, int j) {
    //求区间和
    return sum(j) - sum(i - 1);

}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i].val), a[i].num = i;
    sort(a + 1, a + 1 + n, cmp);//升序
    for (int i = 1; i <= n; i++) {
        a[i].val = i;
        ranks[a[i].num] = a[i].val;//离散化
    }
    for (int i = 1; i <= n; i++) {
        add(ranks[i], 1);
        ans += i-query(1, ranks[i]);//或ans += query(ranks[i]+1,n);
    }
    cout << ans;
    return 0;
}

四. 递推

1.火车站

分析:读完题目发现,唯一一个未知量是第二站上车人数,已知量是最后一站下车m人,也就是倒数第二站发车的时候有m人。本题的递推公式题目也很清楚的说明了。所以遍历b的值,找到答案。

#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define int long long
//#include<bits/stdc++.h>
const int N = 1e6 + 10;

int dp[N];
int getin[N],getoff[N];//每站上车人数
const int mod = 10007;
signed main()
{
    int a, n, m, x;
    cin >> a >> n >> m >> x;
    dp[1] = a, dp[2] = a;
    getin[1] = a;
    getoff[1] = 0;
    //关键在于第二站上车多少个人不知道,因此要逆推
    //设为b吧,b<=a
    for (int b = 0; b <= 10005; b++) {
        getin[2] = b, getoff[2] = b;
        for (int i = 3; i <= n; i++) {
            getin[i] = getin[i - 1] + getin[i - 2];
            getoff[i] = getin[i - 1];
            dp[i] = dp[i - 1] + getin[i] - getoff[i];

        }
        if (dp[n - 1] == m) {
            cout << dp[x];
            return 0;
        }
    }


    return 0;
}

2. 平面分割 

分析:

当线交于一点时,每增加一条线,增加两块区域

当线不交于一点时,每增加一条线,最多增加n块区域(n是加上新加的线后,总共n条线)

int dp[N];
const int mod = 10007;
signed main()
{
    int n, p;
    cin >> n >> p;
    int ans = 2 * p;
    for (int i = p + 1; i <= n; i++) {
        ans += i;
    }
    cout << ans;
    return 0;
}

3.兔子繁殖

分析:兔子出生的那月算第一个月 ,那么要第三个月才开始生兔宝宝。

递推公式:当前月兔子 = 上个月兔子 + 这个月成年兔生的幼兔

上个月兔子好知道,就是dp[i-1]

但是这个月成年兔子是多少?毕竟只有知道了成年兔,才知道生了多少幼兔。

那么dp[i-2]就代表这个月的成年兔。

因为 i-2 月的兔子有成年兔,也有幼兔,但是两个月过后,那些幼兔都已经长成成年兔了,所以i月的成年兔是dp[i-2]。

#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define int long long
//#include<bits/stdc++.h>
const int N = 1e6 + 10;

int a[100][100];
int dp[1005];

signed main()
{
    int n;
    cin >> n;
    dp[1] = 1, dp[2] = 1;
    for (int i = 3; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    cout << dp[n];
    return 0;
}

4.昆虫繁殖

傻逼题目,那句话应该是:每对成虫过x月,每月产y对卵。

这题根上一题略有不一样,因为上一题求的是所有兔子的数量(也包括幼兔) 

但这题求的是成虫数量(不包括卵),相当于求的是上一题成年兔的数量

注意!卵要过两个月长到成虫,然后成虫要再过x个月才能开始产卵

#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define int long long
//#include<bits/stdc++.h>
const int N = 1e6 + 10;

int c[N], r[N];//每月成虫数和卵数
signed main()
{
    int x, y, z;
    //间隔,每次产卵数,z月后(注意是z月后,不是第z月。本题把第0月当成第一个月)
    cin >> x >> y >> z;
    for (int i = 0; i <= x - 1; i++) {
        c[i] = 1;
        r[i] = 0;
    }
    c[x] = 1;
    r[x] = y;
    for (int i = x + 1; i <= z; i++) {
        r[i] = c[i - x] * y;
        c[i] = c[i - 1] + r[i - 2];
    }
    cout << c[z];
    return 0;
}

五. 动态规划

较常见的动态规划有三种题型:

可选的物品数量有限:01背包,多重背包

可选的物品数量无限:完全背包

注意!对于背包容量/预算金额这样的元素

如果是01背包和多重背包,背包容量要从后往前遍历

如果是完全背包,背包容量要从前往后遍历

还会有背包问题的变式,需要具体问题具体分析

至于其他诸多类型的dp,很多跟背包问题类似或者能利用背包问题的思维来解决

1.Bone Collector

 

骨头数,袋子体积

骨头价值

骨头体积

求能带走最大价值是多少

分析:设一个dp[i][j],i代表若有i个骨头,j代表若有j大小的空间。

跟一般的一维dp不同,比如说求的是“第n天最多有多少只兔子”,那么i就代表第i天,dp[i]代表第i天最多的兔子。我们输入的只有一个变量——天数

但是这题不一样,我们输入的有两个属性,骨头数和袋子体积。因此要用二维dp记录这两个变量各种变化的时候所代表的最大价值。

设一个dp[i][j],i代表若有i个骨头,j代表若有j大小的空间。dp[i][j]代表当骨头有i个,袋子体积为j时能带走的最大价值

本题属性:骨头数,袋子体积

#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define int long long
//#include<bits/stdc++.h>
const int N = 1e6 + 10;
int dp[1010][1010];//前i个物品,目前背包容量
struct Bone {
    int val, v;
}bone[1010];
signed main()
{
    int T;
    cin >> T;
    while (T--) {
        int num, bag;
        cin >> num >> bag;
        for (int i = 1; i <= num; i++) {
            cin >> bone[i].val;
        }
        for (int i = 1; i <= num; i++) {
            cin >> bone[i].v;
        }
        memset(dp, 0, sizeof(dp));

        for (int i = 1; i <= num; i++) {
            for (int j = 0; j <= bag; j++) {
          //骨头体积有可能为0,很坑。所以背包容量为0时也有可能装骨头

                if (bone[i].v > j) {
                    //当前骨头体积肯定装不下,没有选择余地
                    dp[i][j] = dp[i - 1][j];
                }
                else {
                    //可以选择要不要装当前这块骨头
                    dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - bone[i].v] + bone[i].val);
                }
            }
        }
        cout << dp[num][bag] << endl;
    }
    
    return 0;
}

二维dp可以压缩成一维dp。

这是二维dp的表格(虽然不是这题的)。

再看二维dp公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - bone[i].v] + bone[i].val);

假设i是行,j是列。发现只用到了第i-1行,而1---(i-1)行已经没用了,到后面也没用,所以可以一直覆盖来压缩成一维。

公式:dp[j] = max (dp[j] , dp[j-c[i].v] + c[i].w);

                                这个dp[j]就相当于dp[i-1][j]

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int dp[1111];
struct node
{
	int v,w;
}c[1111];
int main()
{
	int u;
	int n,v;
	scanf ("%d",&u);
	while (u--)
	{
		scanf ("%d %d",&n,&v);
		memset (dp,0,sizeof (dp));
		for (int i = 1 ; i <= n ; i++)		//读题要注意,这俩别反了 
			scanf ("%d",&c[i].w);
		for (int i = 1 ; i <= n ; i++)
			scanf ("%d",&c[i].v);
		for (int i = 1 ; i <= n ; i++)
		{
			for (int j = v ; j >= c[i].v ; j--)
			{
				dp[j] = max (dp[j] , dp[j-c[i].v] + c[i].w);
			}
		}
		printf ("%d\n",dp[v]);
	}
	return 0;
}

2. Coin Change

思路跟上一题一模一样,有两个限制条件,二维dp

但这题是完全背包问题,即可选的硬币有无限个

#include<iostream>
using namespace std;

const int N = 1e6 + 10;

//只用前i(5)个硬币,金额(250)
int dp[1000][1000];//1,5,10,25,50
int coin[6] = { 0,1,5,10,25,50 };
signed main()
{
    int n;

    memset(dp, 0, sizeof(dp));

    for (int i = 0; i <= 5; i++) {
        dp[i][0] = 1;
    }

    for (int i = 1; i <= 5; i++) {
        for (int j = 1; j <= 251; j++) {
            if (coin[i] > j)
            {
                dp[i][j] = dp[i - 1][j];
            }
            else {
                dp[i][j] += dp[i - 1][j];
                dp[i][j] += dp[i][j - coin[i]];
            }
        }
    }

    while (cin >> n) {
        cout << dp[5][n] << endl;

    }

    return 0;
}

一维写法。

注意!这题跟上一题不一样,这题求的是最大方案数, 因此选和不选的方案要加起来然后放到dp[j]

//前i(5)个硬币,金额(250)
int dp[1000];//1,5,10,25,50
int coin[6] = { 0,1,5,10,25,50 };
signed main()
{
    int n;

    memset(dp, 0, sizeof(dp));

    dp[0] = 1;

    for (int i = 1; i <= 5; i++) {
        for (int j = 1; j <= 251; j++) {
            //如果写成for (int j = coin[i]; j <= 251; j++),这个if的判断就可以省略了
            if (coin[i] > j)
            {
                continue;//不操作,dp[j]没变,也就是dp[i-1][j]
            }
            else {
                //有得选择
                dp[j] = dp[j] + dp[j - coin[i]];
                //注意,题目求的是方案数,所以当前最大方案数 = 选的方案数+不选的方案数
            }
        }
    }

    while (cin >> n) {
        cout << "答案:" << dp[n] << endl;

    }

    return 0;
}

但如果加上个限制条件,选的硬币不能超过100枚,总共有三个限制条件,就一定要用上面的一维dp进行优化,再加个变量,即已选择的硬币数k,然后变成二维dp。

其本质上是三维dp!!!

#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define int long long
//#include<bits/stdc++.h>
const int N = 1e6 + 10;

int coin[5] = { 1,5,10,25,50 }/*下标从零开始,前面加个0*/;
int dp[110][260];//最多用上k个硬币,金额j
signed main() {
    int n;
    while (cin >> n) {
        memset(dp, 0, sizeof(dp));
        dp[0][0] = 1;

        for (int i = 0; i < 5; i++) {//物种货币
            for (int k = 1; k <= 100; k++) {
                for (int v = coin[i]; v <= n; v++) {
                    dp[k][v] += dp[k - 1][v - coin[i]];

                }
            }
        }
        int ans = 0;
        for (int k = 0; k <= 100; k++) {//切记!n可能为0,一枚硬币都不用选!所以k=0不能漏

            ans += dp[k][n];
        }
        cout << ans << endl;
    }
    return 0;
}

3.庆功会

#include <vector>
#include<string>
#include<algorithm>

#include<stdio.h> 
#include<string.h>
#include<stdlib.h>
//#define int long long
#include<math.h>


#include<iostream>
using namespace std;

const int N = 1e6 + 10;

struct Node {
	int price, val, num;
	//价格,价值,可购买数量
}node[7000];
int dp[7000];//预算,购买数量
int main()
{
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> node[i].price >> node[i].val >> node[i].num;
	}

	for (int i = 1; i <= n; i++) {//物品
		for (int j = m; j >= node[i].price; j--) {//一定要从后往前
			for (int k = 0; k <= node[i].num && k * node[i].price <= j; k++) {
				//第i件物品可购买的数量

					//预算
				dp[j] = max(dp[j], dp[j - k * node[i].price] + k * node[i].val);
			}
		}

	}
	cout << dp[m];

	return 0;
}

3. Frog

Sample

InputcopyOutputcopy
 
1 
4 1 2 2 
1 2 3 4 
 
8

题意:从头开始只能往后跳,最多跳k下,求能吃最大的分数

分析:跟上一题的变式一样,本题是本质上三维。

本题的属性:池塘长度,可跳跃步数

dp[i][j],i代表池塘的长度为i(i<=n),j代表可以跳j步(j<=k),dp[i][j]代表当池塘长度为i,可以跳j步时,青蛙能吃的最多的分数。

还有一个地方要强调,例如图中的j的循环,因为要用到i,所以一定要嵌套在i的循环里面

#include<iostream>
#include<cstdio>
#include<algorithm>
#define N 105

using namespace std;

int dp[N][N];//池塘长度,跳跃步数
int book[N];

int main()
{
	int n,A,B,K,T;
	scanf("%d",&T);
	while(T--)
	{
		int sum=0;
		scanf("%d%d%d%d",&n,&A,&B,&K);
		for(int i=0;i<n;i++)
		{
			scanf("%d",&book[i]);
		}
		dp[0][0]=book[0];//别忘了一开始的初始化!

		for(int i=A;i<n;i++)//池塘最短要为A(青蛙跳跃最短的距离),否则青蛙跳不了
		{
			for(int k=1;k<=K;k++)//k=0相当于青蛙没跳,可以从1开始(从0开始也无所谓)
			{
				for(int j=A;j<=i&&j<=B;j++)//每一步青蛙可以跳的距离是[A,B]
				{
					dp[i][k]=max(dp[i-j][k-1]+book[i],dp[i][k]);
				}
			}
		}
		for(int i=1;i<=K;i++)
		{
			sum=max(dp[n-1][i],sum);
		}
		printf("%d\n",sum);
	}
	return 0;
}

4.Common Subsequence

最长公共子序列(LCS)模板题

#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define int long long
//#include<bits/stdc++.h>
const int N = 1e6 + 10;

//int dp[2000][2000];//属性:s1长度,s2长度
int longestCommonSubsequence(string text1, string text2) {
    int dp[1005][1005] = { 0 };
    //属性:test1的长度,test2的长度
    for (int i = 1; i <= text1.size(); i++) {
        for (int j = 1; j <= text2.size(); j++) {
            //比较两个字符串末尾
            if (text1[i - 1] == text2[j - 1]) {
                //如果相等,公共子序列长度肯定加1
                dp[i][j] = dp[i - 1][j - 1] + 1;
            }
            else {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[text1.size()][text2.size()];

}
signed main() {
    string s1, s2;
    while (cin >> s1 >> s2) {
        //求最长公共子序列
        
        cout << longestCommonSubsequence(s1, s2) << endl;
    }

    return 0;
}

5.最少拦截系统

简单的贪心

他妈的,真是气死我了,题目看错了两个地方

1.多组数据

2.每组数据开头是n枚导弹

跟个脑残一样不知道哪里错了浪费一堆时间,真的脑残

#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define int long long
//#include<bits/stdc++.h>
const int N = 1e6 + 10;

int dp[30005];
int a[30005];
int state[30005] = { 0 };
//已发射导弹的目前最高发射高度,
    //因为新添加的导弹一定是为了解决之前导弹解决不了的高度
    //所以state里一定是递增的
signed main() {
    int n = 0;
    while (cin >> n) {


        for (int i = 0; i < n; i++) {
            cin >> a[i];
        }


        memset(state, -1, sizeof(state));

        int cnt = 0;
        for (int i = 0; i < n; i++) {
            int k = 0;
            while (k <= cnt && state[k] < a[i]) {
                k++;
            //贪心地找到了state里能解决当前导弹的最小高度系统,然后跳出循环
             
            //如果没找到,说明目前的所有系统都不能解决当前的导弹
            //要加一个系统
            }
            state[k] = a[i];
            if (k > cnt) cnt++;


        }
        cout << cnt << endl;
    }
    return 0;
}

6.Super Jumping! Jumping! Jumping!

分析:求最长严格上升子序列的和

#include<list>
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define int long long
//#include<bits/stdc++.h>
const int N = 1e6 + 10;

int a[N];
int state[30005] = { 0 };
int dp[N];
signed main() {
    int n = 0;
    while (cin >> n && n != 0) {
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
            dp[i] = a[i];//这里就要初始化
        }

        int ans = -1;
        for (int i = 2; i <= n; i++) {
            for (int j = 1; j < i; j++) {
                if (a[i] > a[j]) {
                    dp[i] = max(dp[j] + a[i], dp[i]);
                }
            }
            ans = max(ans, dp[i]);
        }
        cout << ans << endl;
    }
    return 0;
}

7.ACM排名

#include <vector>
#include<string>
#include<algorithm>

#include<stdio.h> 
#include<string.h>
#include<stdlib.h>
//#define int long long
#include<math.h>


#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
const int N = 1e6 + 10;

int a[N];
int dp[N];//预算
struct Node {
	int score, cost;
}node[10005];
int main()
{
	int t, n;
	cin >> t >> n;
	for (int i = 1; i <= n; i++) {
		cin >> node[i].score >> node[i].cost;
	}

	
	for (int i = 1; i <= n; i++) {
		//完全背包问题(无限个),是从前往后遍历
		//01背包和多重背包都是有限个,是从后往前遍历
		for (int j = node[i].cost; j <= t; j++) {//时间
			dp[j] = max(dp[j], dp[j - node[i].cost] + node[i].score);
		}
	}

	cout << dp[t];
	return 0;
}

8.分组问题

样例输入 Copy
10 6 3
2  1  1
3  3  1
4  8  2
6  9  2
2  8  3
3  9  3
样例输出 Copy
20

#include<map>
#include <vector>
#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define int long long
//#include<bits/stdc++.h>
const int N = 1e6 + 10;

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


struct Node {
	vector<int> w;//重量
	vector<int> v;//价值

}node[1000];
int dp[300];//容量
signed main()
{
	int bag, n, t;
	cin >> bag >> n >> t;
	for (int i = 1; i <= n; i++) {
		int w, v, num;
		cin >> w >> v >> num;
		node[num].w.push_back(w);
		node[num].v.push_back(v);
	}

	//多重背包+01背包,有限个数,容量从后往前
	for (int i = 1; i <= t; i++) {//组
		for (int k = bag; k >= 0; k--) {
			for (int j = 0; j < node[i].w.size(); j++) {//遍历组内的操作必须在内层
				//一组里只能选一个
				int weight = node[i].w[j];
				int val = node[i].v[j];
				if (weight <= k) {
					dp[k] = max(dp[k], dp[k - weight] + val);
				}
			}
		}
	}
	cout << dp[bag];
	return 0;
}

9.打包

样例输入 Copy
6 5 
4 
10 2 2 
20 3 2 
40 4 3 
30 3 3
样例输出 Copy
50

二维费用背包模板题

#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>
#include<iostream>
using namespace std;
#define inf 0x3f3f3f3f
#define int long long
//#include<bits/stdc++.h>
const int N = 1e6 + 10;

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


struct Node {
	int score, w, v;

}node[1000];
int dp[1000][1000];//体积,重量
signed main()
{
	int V, W;
	cin >> W >> V;
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		int a, b, c;
		cin >> a >> b >> c;
		node[i] = { a,b,c };
	}

	//01背包,每个物品只有一件,从后往前遍历
	for (int i = 1; i <= n; i++) {//物品
		for (int j = V; j >= node[i].v; j--) {//体积
			for (int k = W; k >= node[i].w; k--) {//重量
				dp[j][k] = max(dp[j][k], dp[j - node[i].v][k - node[i].w] + node[i].score);
			}

		}
	}
	cout << dp[V][W];
	return 0;
}

六. 字符串操作和哈希表

1.Post Robot

Sample

InputcopyOutputcopy
Apple bananaiPad lemon ApplepiSony
233
Tim cook is doubi from Apple
iPhoneipad
iPhone30 is so biiiiiiig Microsoft
makes good App.
MAI MAI MAI!
MAI MAI MAI!
MAI MAI MAI!
SONY DAFA IS GOOD!
MAI MAI MAI!
MAI MAI MAI!
MAI MAI MAI!

#include<set>
#include<list>
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>

#define inf 0x3f3f3f3f
#define int long long
const int N = 20017;
//#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#include<iostream>
using namespace std;

string word[5] = { "Apple","iPhone","iPod","iPad","Sony" };
signed main()
{
	string str;
	while (getline(cin, str)) {
		istringstream s(str);
		string s2;
		while (s >> s2) {
			int flag = 0;
			for (int i = 0; i < 5; i++) {
				if (s2.find(word[i]) != -1) {
					if (word[i] == "Sony") {
						cout << "SONY DAFA IS GOOD!" << endl;
					}
					else {
						cout << "MAI MAI MAI!" << endl;
					}

					s2.erase(s2.find(word[i]), word[i].size() - 1);
                    //消除已查询过的关键词
				}
			}

		}
	}

	return 0;
}

2.Four Operations

 3.Remove Two Letters

Sample 1

InputcopyOutputcopy
7
6
aaabcc
10
aaaaaaaaaa
6
abcdef
7
abacaba
6
cccfff
4
abba
5
ababa
4
1
5
3
3
3
1

分析:将相邻的两个字母去掉,然后统计不重复的字符串有多少个。

一开始是用erase和map暴力做的,超时了。

正确的解法应该是找规律。

拿abacaba举例,

如果去掉ab,结果是acaba

如果去掉ba,结果是acaba

稍加验证得知,如果 str[i] == str[i+2],那么去掉str[i],str[i+1]和去掉str[i+1],str[i+2]产生的结果一定是一样的,是有重复的。

因为最大不重复字符串的个数是 n-1个(n是字符串长度),然后统计有重复的情况共cnt种,

答案就是 n-1-cnt

#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>

#define inf 0x3f3f3f3f
#define int long long
const int N = 20017;
//#include <bits/stdc++.h>
typedef long long ll;
#include<iostream>
using namespace std;

signed main()
{
	int T;
	cin >> T;
	while (T--) {
		int n;
		cin >> n;
		string str;
		cin >> str;
		int ans = n - 1;
		for (int i = 0; i < n - 2; i++) {
			if (str[i] == str[i + 2]) ans--;
		}
		cout << ans << endl;
	}

	return 0;
}

4.A-B

方法1:map映射法,哈希

简单分析:数据太大了,2的30次方,不能用数组哈希。所以要用map哈希

这是我自己写的,有一个点wal不知道为什么

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

map<int, int> a;//num,times
int main()
{
	int n, c;
	cin >> n >> c;
	for (int i = 0; i < n; i++) {
		int input;
		cin >> input;
		a[input]++;
	}

	int cnt = 0;
	for (auto it = a.begin(); it != a.end(); it++) {
		if (it->second == 0) continue;
		cnt += it->second * a[c + it->first];
	}
	cout << cnt;
	return 0;
}

这是题解的方法,更简洁 

#include<set>
#include<list>
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>

#define inf 0x3f3f3f3f
#define int long long
const int N = 1e6+10;
//#include <bits/stdc++.h>
typedef long long ll;
#include<iostream>
using namespace std;
int a[N];
map<int, int> mp;
signed main()
{
	//A-B = C
	//A-C = B
	int n, c;
	cin >> n >> c;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
		mp[a[i]]++;
		//a[i] 是A,所以要找一个B才能满足公式
		a[i] -= c;//使A变成B
	}

	int ans = 0;
	for (int i = 0; i < n; i++) {
		ans += mp[a[i]];
	}
	cout << ans << endl;
	return 0;
}

方法2:stl库的lower_bound和upper_bound

#include<cstdio>
#include<set>
#include<list>
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>

#define inf 0x3f3f3f3f
#define int long long
const int N = 1e6+10;
//#include <bits/stdc++.h>
typedef long long ll;
#include<iostream>
using namespace std;
int a[N];
bool cmp(int b, int c) {
	return b < c;//升序
}
signed main()
{
	//A-B = C
	//A-C = B
	int n, c;
	cin >> n >> c;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
		
	}
	sort(a, a + n, cmp);
	int ans = 0;
	for (int i = 0; i < n; i++) {
		int target = a[i] - c;//A-C=B
		auto lowerIt = lower_bound(a, a + n, target);
		auto upperIt = upper_bound(a, a + n, target);
		ans += upperIt - lowerIt;

	}
	cout << ans;
	return 0;
}

方法3:双指针

也是预处理排序,然后正常双指针搜索,本质上和方法2一样。

5.小美的01串翻转

分析:

如何求一个01串的最小权值?

假设头为0或1,可以得到两种假设的操作数,取较小的一个就行

于是我用暴力,wa了

正确做法是双重循环遍历,边遍历边统计。

#include<set>
#include<list>
#include<queue>
#include<math.h>
#include<stdlib.h>
#include<string>
#include<string.h>
#include <stdio.h>
#include<algorithm>
#include<iomanip>
#include<cmath>
#include<sstream>
#include<stack>
#include <utility>
#include<map>
#include <vector>

#define inf 0x3f3f3f3f
#define int long long
const int N = 1e6+10;
//#include <bits/stdc++.h>
typedef long long ll;
#include<iostream>
using namespace std;
int a[N];

signed main()
{
	string s;
	cin >> s;
	int n = s.size();
	int ans = 0;
	


	for (int i = 0; i < n; i++) {
		//从i开始
		int cnt0 = 0, cnt1 = 0;
		for (int j = 0; j < n - i; j++) {
			if (j % 2 == 0) {
				//偶数位
				if (s[i + j] != '0') {
					cnt0++;
				}
				if (s[i + j] != '1') {
					cnt1++;
				}
			}
			else {
				//奇数位
				if (s[i + j] != '1') {
					cnt0++;
				}
				if (s[i + j] != '0') {
					cnt1++;
				}

			}
			ans += min(cnt0, cnt1);
            //在内层。相当于每种情况都有加上
            //但如果起点不变,cnt0和cnt1是可以继续累加的
		}
	}
	cout << ans;

	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值