[Week 20]每日一题(C++,图论,数学,搜索)

Posted witheredsakura

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Week 20]每日一题(C++,图论,数学,搜索)相关的知识,希望对你有一定的参考价值。

T1 [Daimayuan] Collision(C++,多源最短路)

题目描述

\\(siyisss\\) 的王国是由 \\(n\\) 个村镇和 \\(n−1\\) 条双向道路构成的,村镇从 \\(1\\)\\(n\\) 依次编号,每条双向道路连接两个不同的村镇,使得从任意一个村镇出发都可以到达任意一个村镇。接下来请你回答 \\(q\\) 个问题,每次给出两个整数 \\(c\\), \\(d\\),表示现在分别有一个人在村镇 \\(c\\),一个人在村镇 \\(d\\),现在在 \\(c\\) 的人要用最短的时间到达村镇\\(d\\),在村镇 \\(d\\) 的人要以最短的时间到达村镇$ c$,假设两人同时出发,两人的速度也是一样的,每条双向道路的长度也是一样的,请问两人相遇的时候是在某一个村镇,还是在某条双向道路上?

输入描述

第一行输入两个整数 \\(n\\), \\(q\\) 代表村镇的数量和询问的数量

接下来 \\(n−1\\) 行,每行两个整数用来描述一条双向道路

最后 \\(q\\),每行两个整数代表 \\(c\\), \\(d\\)

输出描述

对于每个询问,如果他们在某个村镇相遇,请示出Town,否则输出Road

样例输入1

5 2
1 2
2 3
3 4
4 5
1 3
1 5

样例输出1

Town
Town

样例输入2

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

样例输处2

Town
Road
Town
Town
Town
Town
Road
Road
Road

数据范围

\\(2≤n≤100000\\)

\\(1≤q≤100000\\)

对于每一个询问 \\(1≤c_i<d_i≤n\\)

解题思路

题意是给出一张无向连通图,然后询问\\(q\\)次,每次要求判断\\(c\\),\\(d\\)两人相遇的位置。

根据数据范围,我们需要将每次询问的时间复杂度降至\\(O(1)\\)或者\\(O(log\\ q)\\)

理解题意之后,第一个问题就是如何判断两人相遇的位置:

(1)如果最短路的长度为奇数,那么两人在Road相遇;

(2)如果最短路的长度为偶数,那么两人在Town相遇。

关于最短路的算法,我们最容易想到的是Floyd。但是\\(n\\in [2,100000]\\),直接\\(T\\)飞掉。

回顾题意:\\(n\\)个节点、\\(n-1\\)条边、全连通。

这不就是一棵树嘛。

那么我们要知道树的特点:任意两个节点之间只有一条通路。

我们只需要找出最近公共父节点(LCA),就可以得出两个节点之间的通路长度。

采用倍增寻访算法,时间复杂度为\\(O(qlog\\ q)\\),可以接受。

AC代码如下:

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <string.h>
using namespace std;
const int max_n = 1e5;
const int max_expo = 32;

struct edge  int v, next; edges[max_n * 2];
int tot = -1, heads[max_n + 1];
int dp[max_n + 1][max_expo];
int lg[max_n + 1];
int depth[max_n + 1];


void add_edge(int u, int v) 
	edges[++tot] =  v,heads[u] ; heads[u] = tot;
	edges[++tot] =  u,heads[v] ; heads[v] = tot;


void init(int s, int fa) 
	for (int i = 1; i < lg[depth[s]]; i++) 
		dp[s][i] = dp[dp[s][i - 1]][i - 1];
	

	for (int i = heads[s]; i != -1; i = edges[i].next) 
		int v = edges[i].v;
		if (v != fa) 
			dp[v][0] = s;
			depth[v] = depth[s] + 1;
			init(v, s);
		
	


int lca(int x, int y) 
	if (depth[x] < depth[y]) swap(x, y);
	while (depth[x] != depth[y]) x = dp[x][lg[depth[x] - depth[y]] - 1];
	if (x == y) return x;
	for (int i = lg[depth[x]]; i >= 0; i--) 
		if (dp[x][i] != dp[y][i]) 
			x = dp[x][i];
			y = dp[y][i];
		
	
	return dp[x][0];


int main() 
	int n, q, u, v;
	//cin >> n >> q;
	scanf("%d%d", &n, &q);
	for (int i = 1; i <= max_n; i++) 
		lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
	
	memset(heads + 1, -1, sizeof(int) * n);
	for (int i = 0; i < n - 1; i++) 
		//cin >> u >> v;
		scanf("%d%d", &u, &v);
		add_edge(u, v);
	

	dp[1][0] = 1;
	depth[1] = 1;
	init(1, 1);

	for (int i = 0; i < q; i++) 
		//cin >> u >> v;
		scanf("%d%d", &u, &v);
		int ret = lca(u, v);
		//if ((depth[ret] * 2 - depth[u] - depth[v]) % 2 == 0) cout << "Town" << endl;
		//else cout << "Road" << endl;
		if ((depth[ret] * 2 - depth[u] - depth[v]) % 2 == 0) printf("Town\\n");
		else printf("Road\\n");
	
	return 0;


T2 [Daimayuan] 农田划分(C++,数学,BFS)

题目描述

约翰是一个农场主,他的农场有n块田,编号从 \\(1\\)\\(n\\),这 \\(n\\)块田通过 \\(m\\)条双向道路相连(数据保证这\\(n\\)块田都是联通的),我们假设第\\(i\\)块田会产生 \\(2^ikg\\) 的收益,现在约翰想把农田的工作全部交给自己的两个孩子,划分方式必须满足以下规则:

1.每一块田都需要恰好被分给一个孩子.

2.分给两个孩子的农田必须是联通的.就是说对于任意一个孩子在划分给自己的任意一块田,都可以不经过另外一个孩子的田,到达自己的任意一块田.

3.划分给两个孩子的收益必须尽可能的相等,如果无法相等,年长的孩子会得到大的那一份.

对于第 \\(i\\)块田,如果你要把它分给年长的孩子,请输出A,否则输出B.

题目输入

第一行输入两个整数分别代表 \\(n,m\\) 接下来 \\(m\\)行,每个两个整数\\(u,v\\),代表这两块农田通过一条双向道路直接相连,数据保证没有重边和自环

题目输出

输出一个字符串,代表答案

样例输入1

3 2
1 3
3 2

样例输出1

ABA

样例输入2

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

样例输出2

BABABA

数据范围

\\(2≤n≤3e5,1≤m≤3e5\\)

解题思路

首先,我们要知道:

对于数列\\([1,2,2^2,...,2^n-1,2^n]\\)\\(2^n\\)是大于之前所有的数加和的。

利用这个性质,因为我们要保证年长的孩子分得的土地更多,所以\\(n\\)号田地一定会被分给年长的孩子。

继续利用这个性质,因为我们又要保证划分尽可能的相等,所以\\(n-1\\)号田地一定会被划分给另外一个孩子。

这有什么用呢?

我们的具体算法是这样的:

删除\\(n\\)号田地及与其相连的所有通路,然后从\\(n-1\\)号田地开始\\(BFS\\),所有被搜索到的田地均分给另外一个孩子,剩下的田地归年长的孩子。

算法时间复杂度为\\(O(n)\\)

最后,AC代码如下:

#include <iostream>
#include <queue>
#include <string.h>
using namespace std;
const int max_n = 3e5;
const int max_m = 3e5;

struct edge  int v, next; edges[max_m * 2];
int tot = -1, heads[max_n + 1];
queue<int>q;
bool book[max_n + 1];

void add_edge(int u, int v) 
	edges[++tot] =  v,heads[u] ; heads[u] = tot;
	edges[++tot] =  u,heads[v] ; heads[v] = tot;


void bfs(int s) 
	q.push(s);//初始化

	//bfs主体
	while (!q.empty()) 
		int head = q.front();
		q.pop();//取出首节点

		if (book[head]) continue;
		book[head] = true;//访问判断

		for (int i = heads[head]; i != -1; i = edges[i].next) //进行尝试
			int v = edges[i].v;
			if (book[v]) continue;//重复访问
			q.push(v);
		
	


int main() 
	int n, m, u, v;
	cin >> n >> m;
	memset(heads + 1, -1, sizeof(int) * n);
	for (int i = 0; i < m; i++) 
		cin >> u >> v;
		if (u == n || v == n) continue;//逻辑删除
		add_edge(u, v);
	
	bfs(n - 1);
	for (int i = 1; i <= n; i++) 
		if (book[i]) cout << \'B\';
		else cout << \'A\';
	
	return 0;


T3 [Daimayuan] 三段式(C++,数组前缀和)

有一个长度为\\(n\\)的序列,现在我们想把它切割成三段(每一段都是连续的),使得每一段的元素总和都相同,请问有多少种不同的切割方法

输入描述

第一行给出一个数\\(n\\),(\\(1≤n≤10^5\\))

第二行给出序列\\(a_1\\)\\(a_2\\)\\(a_3\\),...,\\(a_n\\),(\\(|a_i|≤10^5\\))

输出描述

输出一个数表示有多少种不同的切割方法

样例输入

4
1 2 3 3

样例输出

1

样例解释

可以将它分成第一组\\(1\\)\\(2\\),第二组\\(3\\),第三组\\(3\\)

解题思路

根据题意,很容易得到下面的公式:

\\[sum_1+sum_2+sum_3=sum\\\\ sum_1=sum_2=sum_3\\\\ \\\\ sum_1=sum_2=sum_3=\\frac13sum \\]

要满足题意有两个前提条件(特殊判定):

(1)\\(sum\\ \\%\\ 3==0\\)

(2)\\(n\\ge3\\)

if (n < 3 || sum % 3 != 0) 
    cout << 0 << endl;
    return 0;

然后我们来寻找可能的分割方式。

如果有多于\\(1\\)种的分割方法,那么一定存在子段和为\\(0\\)

与其去找子段和为\\(0\\)的部分,不如转化思维,使用前缀和的概念(经常用于连续区间和问题)。

这样我们就只需要寻找前缀和同为\\(\\frac13sum\\)的部分了。

然后具体问题具体分析,因为题目中只要求分割为三段,所以我们直接找出前后两段\\(\\frac13sum\\)即可。

注意,至少要为中间的\\(\\frac13sum\\)预留一个元素的位置。

long long ans = 0;
for (int i = 1; i <= n - 1; i++) 
	if (pre[i] == subsum) 
		ans += tail_counter[i + 2];
	

其中tail_counter中的元素意义为:\\(i\\)位置及其之后有多少个后缀和为\\(\\frac13sum\\)

最后,AC代码如下:

#include <iostream>
using namespace std;
const int max_n = 1e5;

long long arr[max_n + 1], pre[max_n + 1], tail[max_n + 2];
long long tail_counter[max_n + 1];

int main() 
	int n;
	long long sum = 0;
	cin >> n;
	for (int i = 1; i <= n; i++) 
		cin >> arr[i];
		sum += arr[i];
	

	if (n < 3 || sum % 3 != 0) 
		cout << 0 << endl;
		return 0;
	
	long long subsum = sum / 3;
	for (int i = 1; i <= n; i++) 
		pre[i] = arr[i] + pre[i - 1];
	
	for (int i = n; i >= 1; i--) 
		tail[i] = arr[i] + tail[i + 1];
		tail_counter[i] = tail_counter[i + 1];
		if (tail[i] == subsum) tail_counter[i]++;
	
	long long ans = 0;
	for (int i = 1; i <= n - 1; i++) 
		if (pre[i] == subsum) 
			ans += tail_counter[i + 2];
		
	
	cout << ans << endl;
	return 0;


T4 [Daimayuan] 模拟输出受限制的双端队列(C++,模拟)

给你一个输出受限的双端队列,限制输出的双端队列即可以从一端插入元素,弹出元素,但是另一端只可以插入不可以删除元素。即每次你可以执行以下三种操作的其中一种:

  1. 在左边压入一个字符
  2. 在右边压入一个字符
  3. 弹出最左边的字符

现在给你 \\(n\\) 个字符作为队列的输入,请问最多有多少可能的出队次序,并按字典序打印这些出队次序。

输入格式

第一行一个数 \\(n\\),表示输入的长度

第二行一个长度为 \\(n\\) 的字符串

输出格式

第一行一个整数 \\(k\\),表示可能的出队方案数

下面 \\(k\\) 行,按字典序输出每种出队方案

样例输入

3
123

样例输出

6
123
132
213
231
312
321

数据规模

对于全部数据保证 \\(1≤n≤7\\)

解题思路

虽然题目说是模拟,但是我们不尝试一下别的方法肯定不会甘心的,所以尝试理解序列满足的规则。

以样例输入为例子,我们留出三个位置\\(□□□\\)

考虑一种简单的情况,我们在所有元素入队之后再进行出队操作,那么一下逻辑成立:

首先对于\\(a_1\\),我们可以任意指定它的位置;

然后对于\\(a_2\\),因为\\(a_1\\)的位置已经确定了,\\(a_2\\)必须在\\(a_1\\)的前方或者\\(a_1\\)的后方,其位置选择受限;

同理可知\\(a_3\\)位置选择同样受限。

似乎并没有可以用来优化代码的规律存在,所以我们还是老老实实回去模拟。

模拟双端队列采用STL提供的deque容器,由于搜索的结果存在重复,采用set存储结果去重(同时set会自动将结果按字典序排序)。

#include <iostream>
#include <deque>
#include <set>
using namespace std;
const int max_n = 7;

string in_str;
int n;
set<string>ans;

采用dfs搜索可能的出队方案:

void dfs(int step, deque<char>d, string str) //dfs状态
	//终止条件
	
	//dfs主体
	//返回后操作

接下来我们对dfs代码功能进行实现。

实现具体的代码之前我们需要知道每一步有几种可能的操作:

1)在首部插入一个元素;

d.push_front(in_str[step]);
dfs(step + 1, d, str);
d.pop_front();

2)在尾部插入一个元素;

d.push_back(in_str[step]);
dfs(step + 1, d, str);
d.pop_back();

3)出队一个元素;

while (!d.empty()) 
	str += d.front(); d.pop_front();
	//在首部插入一个元素
	d.push_front(in_str[step]);
	dfs(step + 1, d, str);
	d.pop_front();
	//在尾部插入一个元素
	d.push_back(in_str[step]);
	dfs(step + 1, d, str);
	d.pop_back();

以上步骤实现完后,终止条件显而易见:

if (step == n) 
	while (!str.empty()) 
		str += d.front(); d.pop_front();
		ans.insert(str);
	
	return;

dfs完整代码如下:

void dfs(int step, deque<char>d, string str) //dfs状态
	//终止条件
	if (step == n) 
		while (!str.empty()) 
			str += d.front(); d.pop_front();
			ans.insert(str);
		
		return;
	
	//dfs主体
	//在首部插入一个元素
	d.push_front(in_str[step]);
    dfs(step + 1, d, str);
    d.pop_front();//返回后操作
    //在尾部插入一个元素
    d.push_back(in_str[step]);
    dfs(step + 1, d, str);
    d.pop_back();//返回后操作
    //出队一个元素
    while (!d.empty()) 
        str += d.front(); d.pop_front();
        //在首部插入一个元素
        d.push_front(in_str[step]);
        dfs(step + 1, d, str);
        d.pop_front();//返回后操作
        //在尾部插入一个元素
        d.push_back(in_str[step]);
        dfs(step + 1, d, str);
        d.pop_back();//返回后操作
    

最后,AC代码如下:

#include<iostream>
#include <set>
#include <deque>
using namespace std;
const int max_n = 7;

int n;
string in_str;
set<string>ans;

void dfs(int step, deque<char>d, string str) //dfs状态
	//终止条件
	if (step == n) 
		while (!d.empty()) 
			str += d.front(); d.pop_front();
		
		ans.insert(str);
		return;
	
	//dfs主体
	//在首部插入一个元素
	d.push_front(in_str[step]);
	dfs(step + 1, d, str);
	d.pop_front();//返回后操作
	//在尾部插入一个元素
	d.push_back(in_str[step]);
	dfs(step + 1, d, str);
	d.pop_back();//返回后操作
	//出队一个元素
	while (!d.empty()) 
		str += d.front(); d.pop_front();
		//在首部插入一个元素
		d.push_front(in_str[step]);
		dfs(step + 1, d, str);
		d.pop_front();//返回后操作
		//在尾部插入一个元素
		d.push_back(in_str[step]);
		dfs(step + 1, d, str);
		d.pop_back();//返回后操作
	


int main() 
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	deque<char>d;
	cin >> n;
	cin >> in_str;
	dfs(0, d, "");
	cout << ans.size() << endl;
	for (auto iter : ans) cout << iter << endl;
	return 0;


T5 [Daimayuan] 简单差分(C++,线段树)

给定一个长度为 \\(n\\) 数组 \\(A\\),执行以下操作 \\(m\\) 次:

选择一段区间 \\([l,r]\\),将区间中所有的数加上整数 \\(x\\)

操作完成后回答 \\(k\\) 个问题:

每个问题给定一段区间 \\([l,r]\\),输出区间中所有数的和。

输入格式

第一行三个正整数 \\(n,m,k\\)

接下来一行 \\(n\\) 个数,表示数组 \\(A\\)

接下来 \\(m\\) 行,每行输入三个整数 \\(l,r,x\\)

接下来 \\(k\\) 行,每行输入两个整数 \\(l,r\\)

输出格式

输出 \\(k\\) 行,每行一个数表示对应问题的和。

样例输入

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

样例输出

18

数据规模

对于全部数据,保证 \\(1≤n≤2×10^5\\)\\(1≤m,k≤10^5\\)\\(|x|≤10^5\\)

解题思路

线段树模板题,不做过多解释。

不了解线段树的可以看一下这个dalao的博客:线段树详解 - Xenny - 博客园

AC代码如下:

#include <iostream>
using namespace std;
const int max_n = 2e5;
const int max_m = 1e5;
const int max_k = 1e5;
const int max_x = 1e5;

long long tree[max_n * 4 + 1], arr[max_n + 1];
long long lazy_tags[max_n * 4 + 1];

void build_tree(int idx, int l, int r) 
	if (l == r) 
		tree[idx] = arr[l];
		return;
	

	int m = l + r >> 1;
	build_tree(idx << 1, l, m);
	build_tree((idx << 1) + 1, m + 1, r);
	tree[idx] = tree[idx << 1] + tree[(idx << 1) + 1];


void push_down(int idx, int l, int r) 
	if (lazy_tags[idx]) 
		int m = l + r >> 1;
		lazy_tags[idx << 1] += lazy_tags[idx];
		tree[idx << 1] += lazy_tags[idx] * (long long)(m - l + 1);
		lazy_tags[(idx << 1) + 1] += lazy_tags[idx];
		tree[(idx << 1) + 1] += lazy_tags[idx] * (long long)(r - m);
		lazy_tags[idx] = 0;
	


void update(int idx, int l, int r, int L, int R, long long val) 
	if (L <= l && r <= R) 
		tree[idx] += (long long)(r - l + 1) * val;
		lazy_tags[idx] += val;
		return;
	
	push_down(idx, l, r);
	int m = l + r >> 1;
	if (L <= m) update(idx << 1, l, m, L, R, val);
	if (R >= m + 1) update((idx << 1) + 1, m + 1, r, L, R, val);
	tree[idx] = tree[idx << 1] + tree[(idx << 1) + 1];


long long query(int idx, int l, int r, int L, int R) 
	if (L <= l && r <= R) 
		return tree[idx];
	
	push_down(idx, l, r);
	int m = l + r >> 1;
	long long sum = 0;
	if (L <= m) sum += query(idx << 1, l, m, L, R);
	if (R >= m + 1) sum += query((idx << 1) + 1, m + 1, r, L, R);
	return sum;


int main() 
	int n, m, k;
	cin >> n >> m >> k;
	for (int i = 1; i <= n; i++) cin >> arr[i];
	build_tree(1, 1, n);
	int l, r, x;
	for (int i = 0; i < m; i++) 
		cin >> l >> r >> x;
		update(1, 1, n, l, r, x);
	
	for (int i = 0; i < k; i++) 
		cin >> l >> r;
		cout << query(1, 1, n, l, r) << endl;
	
	return 0;


T6 [Daimayuan] 非递减的01序列(C++,前缀和)

题面

\\(huaji\\)有一个 01 序列,每次可以对其中的某一位取反(\\(0\\)\\(1\\)\\(1\\)\\(0\\)

求最少翻转其中的几位可以使得该序列变为非递减序列

输入格式

第一行输入一个整数 \\(n\\) (\\(1≤n≤10^6\\))

第二行输入一个长度为 \\(n\\) 的且仅包含 01 的字符串

输出格式

输出一个整数,为该序列变为非递减序列的最少操作次数

输入样例

6
010110

输出样例

2

解题思路

其实题意描述有一点问题,求的应该是将该序列变为单调不减的01序列(因为没有单调性其实也是非递减)。

我们的目标具体来说就是选定某个位置,之前的均为0,之后的均为1,同时保证与原序列相似性最高。

根据这个目标,我们只需要遍历每一个可能的位置,选出其中需要操作次数最少的就可以了。

问题在于如何快速获取操作次数。

因为只有两种元素:0和1。所以我们只需要知道其中一种的数量,剩下的都是另外一种。

那么采用前缀和的概念,累计指定位置前\\(0\\)的数量。

//首元素特殊处理
if (str[0] == \'0\') zero_counter[0] = 1;
else one_counter[0] = 1;

for (int i = 1; i < len; i++) 
	zero_counter[i] = zero_counter[i - 1];
	if (str[i] == \'0\') zero_counter[i]++;

然后遍历每一个位置即可:

int ans = zero_counter[len - 1];//目标序列没有0
for (int i = 0; i < len; i++) 
	ans = min(ans, (i + 1 - zero_counter[i]) + (zero_counter[len - 1] - zero_counter[i]));

最后,AC代码如下:

#include <iostream>
using namespace std;
const int max_len = 1e6;

int zero_counter[max_len];

int main() 
	int n;
	cin >> n;
	string str;
	cin >> str;
	int len = str.size();
	if (str[0] == \'0\') zero_counter[0] = 1;
	for (int i = 1; i < len; i++) 
		zero_counter[i] = zero_counter[i - 1];
		if (str[i] == \'0\') zero_counter[i]++;
	
	int ans = zero_counter[len - 1];//目标序列没有0
	for (int i = 0; i < len; i++) 
		ans = min(ans, (i + 1 - zero_counter[i]) + (zero_counter[len - 1] - zero_counter[i]));
	
	cout << ans << endl;
	return 0;


T7 [Daimayuan] 数学(C++,最大公约数)

给定整数 \\(n\\),胖教授想将\\(1∼n\\)\\(n\\)个数字分成两组,每一组至少有一个数,并且使得两组数字的和的最大公约数最大,请输出最大的最大公约数。

输入格式

一行一个整数\\(n\\)

输出格式

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

样例输入

6

样例输出

7

数据规模

对于\\(20\\%\\)的数据,保证\\(n≤100\\)

对于\\(100\\%\\)的数据,保证\\(n≤10^9\\)

解题思路

我们从最大公约数的概念入手:

\\(x\\)\\(y\\)的最大公约数是\\(z\\),那么\\(x\\)\\(y\\)都是\\(z\\)的倍数。

那么我们所要寻找的最大公约数一定能够整除\\(sum=(1+n)*n/2\\)

要找到最大的最大公约数,我们就只需要找到一个最小的数,使其能整除\\(sum\\)即可。

AC代码如下:

#include<iostream>
using namespace std;

int main() 
	long long n;
	cin >> n;
	long long sum = (n + 1) * n / 2;
	for (long long i = 2; i * i <= sum; i++)
		if (sum % i == 0) 
			printf("%lld", sum / i);
			break;
		
	return 0;


T8 [Daimayuan] pSort(C++,强连通分量)

题目描述

有一个由 \\(n\\) 个元素组成的序列 \\(a_1,a_2,…,a_n\\);最初,序列中的每个元素满足 \\(a_i=i\\)

对于每次操作,你可以交换序列中第 \\(i\\) 个元素和第 \\(j\\) 个元素当且仅当满足 \\(|i−j|=d_i\\)

题目给出序列 \\(b_1,b_2,…,b_n\\)\\(d_1,d_2,…,d_n\\),询问是否可以通过若干次交换,使得序列 \\(a\\) 和序列 \\(b\\) 完全相同。

输入格式

\\(1\\) 行一个正整数 \\(n\\),含义如上。

\\(2\\)\\(n\\) 个正整数表示 \\(b_1,b_2,…,b_n\\)

\\(3\\)\\(n\\) 个正整数表示 \\(d_1,d_2,…,d_n\\)

输出格式

若能,输出 YES;否则输出 NO

输入 #1

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

输出 #1

YES

输入 #2

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

输出 #2

NO

数据范围

\\(1≤n,d_i≤100\\)。保证序列 \\(b\\) 中元素不重复。

补充说明

原题:CF28B pSort | 洛谷

解题思路

对于题意的理解如下:

if (abs(i - j) == d[i] || abs(i - j) == d[j]) 
	/* i,j是可交换的 */

那么我们可以把\\(a\\)序列抽象为一张无向图,可交换关系为无向边。

则强连通分量之内的节点可以随意交换。

也就是说,如果需要交换所有节点都在同一个强连通分量之中,就输出YES

反之,如果需要交换的任意一对节点不在一个强连通分量中,就输出NO

接下来实现代码:

我们采用染色法标记不同的强连通分量,用广度优先搜索对整张图进行染色,时间复杂度为\\(O(n^2)\\)

void bfs(int bg) 
	color++;
	q.push(bg);

	int head, next, i;
	while (!q.empty()) 
		head = q.front();
		q.pop();

		if (book[head]) continue;
		book[head] = true;
		colors[head] = color;

		for (i = 1; i <= n; i++) 
			if (i == head) continue;
			next = abs(head - i);
			if (next == ds[head] || next == ds[i]) q.push(i);
		
	

我们需要遍历每一个节点进行尝试,所以算法总时间复杂度为\\(O(n^3)\\),可以接受。

for (i = 1; i <= n; i++) 
	if (!colors[i]) bfs(i);

最后,AC代码如下:

#include <iostream>
#include <queue>
#include <cmath>
using namespace std;
const int max_n = 100;

int bs[max_n], ds[max_n];
int colors[max_n], color = 0;
queue<int>q;
int book[max_n];
int n;

void bfs(int bg) 
	color++;
	q.push(bg);

	int head, next, i;
	while (!q.empty()) 
		head = q.front();
		q.pop();

		if (book[head]) continue;
		book[head] = true;
		colors[head] = color;

		for (i = 1; i <= n; i++) 
			if (i == head) continue;
			next = abs(head - i);
			if (next == ds[head] || next == ds[i]) q.push(i);
		
	


int main() 
	cin >> n;
	int i;
	for (i = 1; i <= n; i++) cin >> bs[i];
	for (i = 1; i <= n; i++) cin >> ds[i];
	for (i = 1; i <= n; i++) 
		if (!colors[i]) bfs(i);
	

	for (i = 1; i <= n; i++) 
		if (colors[bs[i]] != colors[i]) 
			cout << "NO" << endl;
			return 0;
		
	
	cout << "YES" << endl;
	return 0;


T9 [Daimayuan] Owwwwwwwwwww...f(C++,强连通分量)

\\(A\\)地盘上的所有人被从 \\(1\\)\\(n\\) 编号,每个人都有自己传话的对象,第 \\(i\\) 个人对第 \\(a_i\\)个人传话。 有一天,小\\(A\\)在宫殿的顶部大声喊着\\(Owf\\),于是一个有趣的游戏在小\\(A\\)的地盘上开始了。

规则如下:

该游戏有许多轮,每个人都会开始一轮游戏。如果编号为 \\(x\\) 的人想要开始一轮游戏,他会对第 \\(a_x\\)个人说"\\(Oww...wwf\\)"(有 \\(t\\)\\(w\\))。如果 \\(t>1\\),第 \\(a_x\\)个人就会对第 \\(a_ax\\)个人说"\\(Oww...wwf\\)"(有 \\(t−1\\)\\(w\\))。直到有人听到"\\(Owf\\)"(\\(t=1\\)),这个人就是这一轮的\\(Joon\\)。不存在同时进行两轮游戏的情况。 为了使游戏更有意思,小\\(A\\)有一个邪恶的计划。他想找到最小的 \\(t\\)\\(t≥1\\))使得对于每个人 \\(x\\) 当第 \\(x\\) 个人开始的一局游戏使 \\(y\\) 成为了 \\(Joon\\) ,也使得由 \\(y\\) 开始的一局游戏 \\(x\\) 成为 \\(Joon\\) 。请为小\\(A\\)找这个最小的 \\(t\\)。 注意:可能有的人传话对象是自己。

输入格式:

第一行输入一个 \\(n\\) (\\(1≤n≤150\\)),表示小A地盘上的人数。

第二行输入\\(a_1\\)\\(a_2\\)\\(a_3\\),...\\(a_n\\),第 \\(i\\) 个数表示第 \\(i\\) 个人传话的对象 \\(a_i\\)

输出格式:

输出最小的 \\(t\\),如果没有请输出 \\(−1\\)

样例输入:

4
2 3 1 4

样例输出:

3

解题思路:

把题中序列抽象为一张有向图,有\\(n\\)个节点、\\(n\\)条有向边\\(<i,a_i>\\)

如果能够达成题中所述的双向传话,两个人必须在一个环中。

如果环的长度为偶数,那么这个环的传话次数为其长度一半;

如果环的长度为奇数,那么这个环的传话次数为其长度。

我们需要做的就是统计每一个环的传话次数,然后计算最小公倍数即可。

很简单对吧qwq?

那么现在来实现代码。

首先是搜索环的长度:

void dfs(int bg) 
	int next = as[bg], sum = 1;
	book[bg] = true;
	while (next != bg) 
		if (book[next]) 
			fail = true;
			return;
		
		sum++;
		book[next] = true;
		next = as[next];
	
	if (sum % 2 == 0) ans.push_back(sum / 2);
	else ans.push_back(sum);


然后计算所有ans的最小公倍数:

long long ret = 1;
for (auto iter : ans) 
	ret = lcm((long long)(iter), ret);

cout << ret << endl;

后排提醒:/* 十年OI一场空,不开long long见祖宗 */

最后,AC代码如下:

#include <iostream>
#include <vector>
using namespace std;
const int max_n = 150;

int as[max_n + 1];
bool book[max_n], fail = false;
vector<int>ans;

void dfs(int bg) 
	int next = as[bg], sum = 1;
	book[bg] = true;
	while (next != bg) 
		if (book[next]) 
			fail = true;
			return;
		
		sum++;
		book[next] = true;
		next = as[next];
	
	if (sum % 2 == 0) ans.push_back(sum / 2);
	else ans.push_back(sum);


long long gcd(long long x, long long y) 
	long long t;
	while (y != 0) 
		t = x % y;
		x = y;
		y = t;
	
	return x;


long long lcm(long long x, long long y) 
	long long ret = gcd(x, y);
	return x * y / ret;


int main() 
	int n, u, v;
	cin >> n;
	for (int i = 1; i <= n; i++) 
		cin >> as[i];
	
	for (int i = 1; !fail && i <= n; i++) 
		if (!book[i]) 
			dfs(i);
		
	
	if (fail) 
		cout << -1 << endl;
		return 0;
	
	long long ret = 1;
	for (auto iter : ans) 
		ret = lcm((long long)(iter), ret);
	
	cout << ret << endl;
	return 0;


T10 [Daimayuan] 树(C++,动态规划,01背包方案数)

有一棵 \\(n\\) 个节点的以 \\(1\\) 号点为根的有根树。现在可以对这棵树进行若干次操作,每一次操作可以选择树上的一个点然后删掉连接这个点和它的儿子的所有边。

现在我们想知道对于每一个 \\(k\\) (\\(1≤k≤n\\)),最少需要多少次操作能让图中恰好存在 \\(k\\) 个联通块。

输入格式

第一行输入一个正整数 \\(n\\)

第二行输入 \\(n−1\\) 个整数 \\(f_1,f_2,...,f_n−1\\)\\(f_i\\) 表示 \\(i+1\\) 号点的父亲,保证 \\(1≤f_i≤i\\)

输出格式

输出 \\(n\\) 个整数,第 \\(i\\) 个数表示 \\(k=i\\) 时的答案,如果无法让图中恰好存在 \\(i\\) 个联通块,则输出 -1

样例输入1

6
1 2 1 1 2

样例输出1

0 -1 1 1 -1 2

数据规模

\\(10\\) 个测试点。

测试点 \\(1,2,3\\) 满足 \\(n≤20\\)

测试点 \\(4,5,6\\) 满足 \\(n≤100\\)

对于所有数据,满足 \\(1≤n≤3000\\)

解题思路

对于一棵树来说,删去任意一条边都会使连通块数目\\(+1\\)

那么要判断能否得到\\(k\\)个连通块,我们只需要判断能否恰好删去\\(k-1\\)条边。

题目要求操作为:删除一个节点与子节点之间的所有边。

那么统计每个节点的子节点数目,然后就变为了01背包可行性问题:

每一个节点都是一个物品,问能否恰好装满容量为\\(k-1\\)的背包?

for (int i = 1; i <= n; i++) //尝试每一个物品
	for (int j = 0; j < n; j++) //尝试新的重量组合
		if (j - items[i] >= 0)
        	ans[i][j] = ans[i - 1][j] || ans[i - 1][j - items[i]];
    	else
            ans[i][j] = ans[i - 1][j];
    

以上我们只是检验了可行性问题,但是题目中还有另外一个要求:操作次数最少。

因为在物品组合中没有先后顺序,所以我们可以通过物品组合中的物品数量来确定操作次数。

只有新的操作次数小于旧的操作次数的时候,我们才进行更新。

for (int i = 1; i <= n; i++) //尝试每一个物品
	for (int j = 0; j < n; j++) //尝试新的重量组合
        if (j - items[i] >= 0 && ans[i - 1][j] && ans[i - 1][j - items[i]])
        	ans[i][j] = min(ans[i - 1][j], ans[i - 1][j - items[i]] + 1);
    	else if (ans[i - 1][j])
            ans[i][j] = ans[i - 1][j];
        else if (j - items[i] >= 0 && ans[i - 1][j - items[i]])
            ans[i][j] = ans[i - 1][j - items[i]];
    

注:1)以上代码段中,ans中元素的含义发生了变化:可行/不可行 -> 物品数量;

2)为了与不存在的组合(ans[i][j] = 0)相区分,我们为所有存在的组合物品数量添加偏置bias,也就是说,物品数量 = ans[i][j] - bias

以上代码的空间复杂度、时间复杂度均可以接受,可以AC,接下来是优化部分qwq。

因为嫌弃这个算法的空间复杂度,所以我们对其进行优化,压缩到二维数组:

for (int i = 1; i <= n; i++) //尝试每一个物品
	for (int j = 0; j < n; j++) //尝试新的重量组合
        if (j - items[i] >= 0 && ans[(i - 1) % 2][j] && ans[(i - 1) % 2][j - items[i]])
        	ans[i % 2][j] = min(ans[(i - 1) % 2][j], ans[(i - 1) % 2][j - items[i]] + 1);
    	else if (ans[(i - 1) % 2][j])
            ans[i % 2][j] = ans[(i - 1) % 2][j];
        else if (j - items[i] >= 0 && ans[(i - 1) % 2][j - items[i]])
            ans[i % 2][j] = ans[(i - 1) % 2][j - items[i]];
    

还是嫌弃?继续压,压缩到一维数组:

for (int i = 1; i <= n; i++) //尝试每一个物品
	for (int j = n; j >= items[i]; j--) //尝试新的重量组合
		if (j - items[i] >= 0 && ans[j] && ans[j - items[i]])
            ans[j] = min(ans[j], ans[j - items[i]] + 1);
		else if (ans[j]) ans[j] = ans[j];
        else if (j - items[i] >= 0 && ans[j - items[i]])
            ans[j] = ans[j - items[i]] + 1;
	

然后我们删除一些无用的部分:

for (int i = 1; i <= n; i++) //尝试每一个物品
	for (int j = n; j >= items[i]; j--) //尝试新的重量组合
		if (ans[j] && ans[j - items[i]]) ans[j] = min(ans[j], ans[j - items[i]] + 1);
		else if (ans[j - items[i]]) ans[j] = ans[j - items[i]] + 1;
	

嗯,好看多了qwq。

最后,AC代码如下:

#include <iostream>
using namespace std;
const int max_n = 3000;

int ans[max_n + 1], n;
int items[max_n + 1];


int main() 
	cin >> n;
	int fa;
	for (int i = 1; i < n; i++) 
		cin >> fa;
		items[fa]++;
	

	int bias = 1;
	ans[0] = bias;
	for (int i = 1; i <= n; i++) //尝试每一个物品
		for (int j = n; j >= items[i]; j--) //尝试新的重量组合
			if (ans[j] && ans[j - items[i]]) ans[j] = min(ans[j], ans[j - items[i]] + 1);
			else if (ans[j - items[i]]) ans[j] = ans[j - items[i]] + 1;
		
	

	cout << 0;
	for (int i = 2; i <= n; i++) 
		cout << \' \' << ans[i - 1] - bias;
	
	return 0;


将绘图导出到 Plot.ly 时出错

【中文标题】将绘图导出到 Plot.ly 时出错【英文标题】:Errors when exporting plots to Plot.ly 【发布时间】:2015-03-07 20:22:06 【问题描述】:

我有这些数据(前 20 行的样本):

编码变量值 1 Z1 Week.0 0 2 Z2 Week.0 0 3 Z3 Week.0 0 4 Z4 Week.0 0 5 Z5 Week.0 0 6 Z6 Week.0 0 7 Z7 Week.0 0 8 Z8 Week.0 0 9 Z9 Week.0 0 10 Z101 Week.0 不适用 11 Z102 Week.0 不适用 12 Z1 Week.1 0 13 Z2 Week.1 0 14 Z3 Week.1 0 15 Z4 Week.1 0 16 Z5 Week.1 0 17 Z6 Week.1 0 18 Z7 Week.1 0 19 Z8 Week.1 0

我使用以下方法绘制它:

pZ <- ggplot(zmeltdata,aes(x=variable,y=value,color=Codering,group=Codering)) + 
  geom_line()+
  geom_point()+
  theme_few()+
  theme(legend.position="right")+
  scale_color_hue(name = "Treatment group:")+
  scale_y_continuous(labels = percent)+
  ylab("Germination percentage")+
  xlab("Week number")+
  labs(title = "Z. monophyllum germination data")
pZ

图表显示得很好:

然而,当我想将其导出到 Plot.ly 时,我收到以下错误:

> py <- plotly()
> response<-py$ggplotly(pZ)
Error in if (all(xcomp) && all(ycomp))  : 
  missing value where TRUE/FALSE needed
In addition: Warning message:
In trace.list[[lind[1]]]$y == trace.list[[lind[2]]]$y :
  longer object length is not a multiple of shorter object length

我已经搜索了这些错误,但解释完全让我感到困惑。 “需要 TRUE/FALSE 的缺失值。”如果您在流程中使用 IF/ELSE/TRUE/FALSE 等逻辑术语,则应该会发生这种情况,而我根本不会!即使在我得到的图表值中检查任何 NA 时:

> is.na(pZ)
       data      layers      scales     mapping       theme coordinates       facet    plot_env      labels 
      FALSE       FALSE       FALSE       FALSE       FALSE       FALSE       FALSE       FALSE       FALSE 

当您有不同长度的对象时,应该会弹出“较长的对象长度不是较短对象长度的倍数”,但我只使用 1 个具有 3 行长度完全相同的对象。当我询问这些行时,图表的值确实给了我NULL,但这应该会发生..

> nrow(zmeltdata)
[1] 143
> nrow(test)
NULL

总而言之,我很困惑,不知道如何正确处理这些错误,谁能详细说明?

感谢您的宝贵时间。

编辑:我尝试使用 1:100 的随机样本将不同的图表导出到 Plot.ly 并且效果很好,我很确定错误在我的数据中,我只是想不通如何解决它。

EDIT2:回应@Gregor:

> dput(head(zmeltdata, 20))
structure(list(Codering = structure(c(16L, 19L, 20L, 21L, 22L, 
23L, 24L, 25L, 26L, 17L, 18L, 16L, 19L, 20L, 21L, 22L, 23L, 24L, 
25L, 26L), .Label = c("B1", "C2", "C3", "C8", "M1", "M101", "M102", 
"M2", "M3", "M4", "M5", "M6", "M7", "M8", "M9", "Z1", "Z101", 
"Z102", "Z2", "Z3", "Z4", "Z5", "Z6", "Z7", "Z8", "Z9"), class = "factor"), 
    variable = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
    1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L), .Label = c("Week.0", 
    "Week.1", "Week.2", "Week.3", "Week.4", "Week.5", "Week.6", 
    "Week.7", "Week.8", "Week.9", "Week.10", "Week.11", "Week.12"
    ), class = "factor"), value = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 
    NA, NA, 0, 0, 0, 0, 0, 0, 0, 0, 0)), .Names = c("Codering", 
"variable", "value"), row.names = c(NA, 20L), class = "data.frame")

还有尾巴:

> dput(tail(zmeltdata, 43))
structure(list(Codering = structure(c(19L, 20L, 21L, 22L, 23L, 
24L, 25L, 26L, 17L, 18L, 16L, 19L, 20L, 21L, 22L, 23L, 24L, 25L, 
26L, 17L, 18L, 16L, 19L, 20L, 21L, 22L, 23L, 24L, 25L, 26L, 17L, 
18L, 16L, 19L, 20L, 21L, 22L, 23L, 24L, 25L, 26L, 17L, 18L), .Label = c("B1", 
"C2", "C3", "C8", "M1", "M101", "M102", "M2", "M3", "M4", "M5", 
"M6", "M7", "M8", "M9", "Z1", "Z101", "Z102", "Z2", "Z3", "Z4", 
"Z5", "Z6", "Z7", "Z8", "Z9"), class = "factor"), variable = structure(c(10L, 
10L, 10L, 10L, 10L, 10L, 10L, 10L, 10L, 10L, 11L, 11L, 11L, 11L, 
11L, 11L, 11L, 11L, 11L, 11L, 11L, 12L, 12L, 12L, 12L, 12L, 12L, 
12L, 12L, 12L, 12L, 12L, 13L, 13L, 13L, 13L, 13L, 13L, 13L, 13L, 
13L, 13L, 13L), .Label = c("Week.0", "Week.1", "Week.2", "Week.3", 
"Week.4", "Week.5", "Week.6", "Week.7", "Week.8", "Week.9", "Week.10", 
"Week.11", "Week.12"), class = "factor"), value = c(0.1, 0.06, 
0.05, 0.09, 0.04, 0.08, 0.05, 0.08, 0, 0, NA, NA, NA, NA, NA, 
NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 
NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA)), .Names = c("Codering", 
"variable", "value"), row.names = 101:143, class = "data.frame")

我对这些一点也不感到惊讶,数据集中有相当多的 NA,但它们不应该被证明是一个问题,因为我之前使用过类似(更大)的数据集。

如果您愿意,我也有 .csv 文件供您使用:https://www.mediafire.com/?jij1vlp14a29ntt

【问题讨论】:

请以更可复制的格式分享您的数据...如果您使用dput(head(zmeltdata, 20)),它将看起来丑陋但非常有用(我们将能够复制/将其粘贴到 R 中并知道所有列类是什么)。此外,如果您的问题出在您的数据中并且您的前 100 行有效,那么您可能应该发布(或只是检查)最后 43 行数据 (dput(tail(zmeltdata, 43))) 没问题@Gregor:如果您有兴趣,我已经包含了数据文件本身。谨慎起见,病毒扫描:virustotal.com/en/file/… 【参考方案1】:

问题是关于处理 NA 的...我通过运行以下代码得到了 https://plot.ly/~marianne2/417/z-monophyllum-germination-data/:

pZ <- ggplot(na.omit(zmeltdata), aes(x=variable, y=value, color=Codering,
                                 group=Codering)) +
  geom_line() +
  geom_point() +
  # theme_few() +
  theme(legend.position="right") +
  scale_color_hue(name="Treatment group:") +
  # scale_y_continuous(labels = percent) +
  ylab("Germination percentage") +
  xlab("Week number") +
  labs(title="Z. monophyllum germination data")

py$ggplotly(pZ, kwargs=list(fileopt="overwrite", filename="test_zdata"))

请注意,我必须注释掉 theme_few()scale_y_continuous(labels = percent),因为仅加载“ggplot2”会出现以下错误:

Error: could not find function "theme_few"

Error in structure(list(call = match.call(), aesthetics = aesthetics,  : 
object 'percent' not found

分别。我猜这些是依赖问题(也许您使用的是“ggthemes”版本?)。

我不知道theme_few() 有什么魔法,但如果我不在zmeltdata 上使用na.omit(),我的pZ 情节如下所示:

Eww,“Week.10”在“Week.1”之后而不是在“Week.9”之后......所以你无论如何都不想把它发送给 plotly!所以我不能完全重现你的 ggplot 例子。但我想知道你是否真的想保留这些 NA(CSV 本身读取“NA”,我期待空白的“单元格”)。你不想对这些进行预处理吗?

请注意,当我不在 zmeltdata 上使用 na.omit() 时,我会收到以下警告消息:

Warning messages:
1: Removed 20 rows containing missing values (geom_path).
2: Removed 47 rows containing missing values (geom_point).

再次,除了纯粹的显示/绘图考虑之外,由于这看起来像科学数据,您不想用实际数字对周数进行编号,或者如果您真的想要一个字符串则填充数字? (“Week.01”、“Week.02”等) 而且看起来丢失的数据都在尾随... 10+ 周内(还)没有数据,对吧?

感谢您的报告,

玛丽安

【讨论】:

谢谢玛丽安。在我问的上一个问题中,有人建议我使用“长”数据格式而不是“宽”,我自己也不会使用。省略 NA 是有效的,并且允许我将图表导出到 Plot.ly! plot.ly/~YoeriGod/52/z-monophyllum-germination-data 谢谢你:)。

以上是关于[Week 20]每日一题(C++,图论,数学,搜索)的主要内容,如果未能解决你的问题,请参考以下文章

2021夏季每日一题 week7 完结

2021暑假每日一题 week6 完结

2021暑假每日一题 week8 未完结

寒假每日一题2022week1 未完结

寒假每日一题(入门组)week2 完结

寒假每日一题(入门组)week3 完结