8.1集训模拟赛题解

A.折纸

题目描述

小s很喜欢折纸。
有一天,他得到了一条很长的纸带,他把它从左向右均匀划分为\(n\)个单位长度,并且在每份的边界处分别标上数字\(1-n\)
然后小s开始无聊的折纸,每次他都会选择一个数字,把纸带沿这个数字当前所在的位置翻折(假如已经在边界上了那就相当于什么都不做)。
小s想知道 次翻折之后纸带还有多长。

输入格式

输入包含多组数据,第一为一个正整数\(T\)
接下来,每组数据包括两行。
第一行包含两个正整数\(n\)\(m\),表示纸带的长度和操作的次数。
第二行包含\(m\)个整数\(Di\),其中\(Di\)表示第\(i\)次选择的数字。

输出格式

每组数据输出一行,只有一个数字,即纸带最后的长度。

样例

\(input\)

2
5 2
3 5
5 2
3 2

\(output\)

2
2

思路

思路1(并查集模拟)

把折叠后重合的点连起来,最后看有多少个集合并减1(因为是区间),得到答案。

#include <cstdio>
#include <cstring>
#include <iostream>  //long long
#include <algorithm>
using namespace std;
const int maxn = 3e3 + 5, INF = 0x3f3f3f3f;
inline int read() {
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
int n, m, f[10000005];
void Init() {
    for (int i = 1; i <= n; i++) f[i] = i;
}
int Find(int x) {
    if (f[x] == x)
        return x;
    return f[x] = Find(f[x]);
}
void Merge(int a, int b) { f[Find(b)] = Find(a); }
int main() {
    int T = read();
    while (T--) {
        n = read(), m = read();
        Init();
        int l = 0, r = n;
        for (int i = 1, now; i <= m; i++) {
            now = read();
            now = Find(now);
            if (now > r)
                now = r * 2 - now;
            else if (now < l)
                now = l * 2 - now;
            if (now - l > r - now) {
                for (int j = now + 1; j <= r; j++) Merge(now * 2 - j, j);
                r = now;
            } else {
                for (int j = l; j <= now - 1; j++) Merge(now * 2 - j, j);
                l = now;
            }
            // cout<<l<<" "<<r<<" "<<endl;
        }
        cout << r - l << endl;
    }
    return 0;
}

思路2(正解,维护左右端点)

由于每次折叠都是由前一个折叠后的状态转移过来的,所以我们将每次折叠实际位置维护起来,不断更新,要是统一都向右折,根据数据范围显然会炸long long,必须要开unsigned long long,可以都向中间折叠,可以不用开unsigned long long,下代码并没有向中间折叠,实际上方法是一样的。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define rint register int
using namespace std;
const int maxn = 3050;
inline unsigned long long read(){
	unsigned long long x = 0, f = 1; char ch = getchar();
	for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
	for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
	return x * f;
}
int t, m;
unsigned long long n, sol[maxn];
int main(){
	t = read();
	while(t--){
		n = read(), m = read();
		unsigned long long l = 0, r = n;
		for(rint i = 1; i <= m; i++){
			sol[i] = read();
			for(rint j = 1; j < i; j++){
				if(sol[j] <= sol[i]) continue;
				else{
					sol[i] = sol[j] * 2 - sol[i];
				}
			}
			r = max(sol[i] * 2 - l, r);
			l = sol[i];
		}
		printf("%lld\n", r - l);
	}
	return 0;
}

B.water(来源POI1999,略有不同)

题目描述

有一块矩形土地被划分成\(n \times m\)个正方形小块。这些小块高低不平,每一小块都有自己的高度。水流可以由任意一块地流向周围四个方向的四块地中,但是不能直接流入对角相连的小块中。
一场大雨后,由于地势高低不同,许多地方都积存了不少降水。给定每个小块的高度,求每个小块的积水高度。
注意:假设矩形地外围无限大且高度为0。

输入格式

第一行包含两个非负整数n,m。
接下来n行每行m个整数表示第i行第j列的小块的高度。

输出格式

输出n行,每行m个由空格隔开的非负整数,表示每个小块的积水高度。

样例

\(input\)

3 3
4 4 0
2 1 3
3 3 -1

\(output\)

0 0 0
0 1 0
0 0 1

思路

本题目有两种解法,一种是用朴素的宽搜解决问题(考试的时候时间不够懒得写了),另一种是gyz大佬想出来的kruskal的方法(挺难想到的),orz。

思路1

外围高度为0,所以高度为负的至少能够装水到地平,首先能确定储水量的是矩形的四边上的点,对于其他的点就不好直接判断了,但根据水桶原理,能储多少水取决于最短的那块木板,所以我们可以把四边上的每一个点放到一个小根堆里,依次从高度最低的点往里搜,显然比他高的点是存不了水的,比他低的点可以存储他们高度差的水量,依次搜索即可。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 300 + 5, INF = 0x3f3f3f3f, kx[4] = { 0, 1, 0, -1 }, ky[4] = { 1, 0, -1, 0 };
inline int read() {
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
struct Node {
    int x, y, w;
    friend bool operator<(const Node &A, const Node &B) { return A.w > B.w; }
};
int n, m, a[maxn][maxn], h[maxn][maxn], black[maxn][maxn];
priority_queue<Node> q;
int main() {
    n = read(), m = read();
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) h[i][j] = a[i][j] = read();
    for (int i = 0; i <= n + 1; i++) q.push((Node){ 0, i, 0 });
    for (int i = 0; i <= m + 1; i++) q.push((Node){ i, 0, 0 });
    while (!q.empty()) {
        Node now = q.top();
        q.pop();
        int x = now.x, y = now.y, w = now.w;
        for (int i = 0; i < 4; i++) {
            int nx = x + kx[i], ny = y + ky[i];
            if (nx < 0 || ny < 0 || nx > n + 1 || ny > m + 1 || black[nx][ny])
                continue;
            h[nx][ny] = max(h[nx][ny], w);
            black[nx][ny] = 1;
            q.push((Node){ nx, ny, h[nx][ny] });
        }
    }
    for (int i = 1; i <= n; i++, puts(" "))
        for (int j = 1; j <= m; j++) cout << h[i][j] - a[i][j] << " ";

    return 0;
}

思路2

(前方高能,非战斗人员请迅速撤离)
建边用两点中高度最大的点的高度为边权,考虑一个问题,一条路径中最大的边权就是这个坑的最大深度,因为要是有更大的路径都没有选择。本蒟蒻目前还没有完全理解,等悟了之后一定重新缕一边思路,目前只悟了这么点。

#include<bits/stdc++.h>
using namespace std;
const int M=310*310,N=310;
struct Edge{
	int fr,to,nxt,val;
	bool operator < (const Edge&A)const{
		return val<A.val;
	}
}e[M],t[M<<2];
struct Node{
	int x,y;
	Node(){}
	Node(int a,int b){
		x=a;y=b;
	}
	bool operator < (const Node&A)const{
		return x<A.x;
	}
};
map<int,Node> col;
int h[M],idx;
void Ins(int a,int b,int c){
	e[++idx].to=b;e[idx].nxt=h[a];
	h[a]=idx;e[idx].val=c;
}
int n,m;
int Mat[N][N],mp[N][N],ans[N][N],cnt;
int f[M];
int find(int x){
	return x==f[x]?x:(f[x]=find(f[x]));
}
void dfs(int u,int fa,int Mx){
	Node now=col[u];
	if(Mat[now.x][now.y]<=Mx)ans[now.x][now.y]=Mx-Mat[now.x][now.y];
	else ans[now.x][now.y]=0;
	for(int i=h[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa)continue;
		dfs(v,u,max(Mx,e[i].val));
	}
}
int main(){
	scanf("%d%d",&n,&m);
	col[0]=Node(0,0);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&Mat[i][j]);
			mp[i][j]=++cnt;
			col[cnt]=Node(i,j);
		}
	}
	for(int i=1;i<=cnt;i++)f[i]=i;
	cnt=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			t[++cnt].fr=mp[i][j];
			t[cnt].to=mp[i][j+1];
			t[cnt].val=max(Mat[i][j],Mat[i][j+1]);
			t[++cnt].fr=mp[i][j];
			t[cnt].to=mp[i+1][j];
			t[cnt].val=max(Mat[i][j],Mat[i+1][j]);
			t[++cnt].fr=mp[i][j];
			t[cnt].to=mp[i][j-1];
			t[cnt].val=max(Mat[i][j],Mat[i][j-1]);
			t[++cnt].fr=mp[i][j];
			t[cnt].to=mp[i-1][j];
			t[cnt].val=max(Mat[i][j],Mat[i-1][j]);
		}
	}
	sort(t+1,t+cnt+1);
	for(int i=1;i<=cnt;i++){
		int u=find(t[i].fr),v=find(t[i].to);
		if(u!=v){
			f[u]=v;
			Ins(u,v,t[i].val);Ins(v,u,t[i].val);
		}
	}
	dfs(0,-1,-0x7fffffff);
	for(int i=1;i<=n;i++,puts(""))
	for(int j=1;j<=m;j++)
		printf("%d ",ans[i][j]);
}

找伙伴

题目描述

在班级里,每个人都想找学习伙伴。伙伴不能随便找,都是老师固定好的,老师给出要求:伙伴要凭自己实力去找。
老师给每个人发一张纸,上面有数字。假设你的数字是w,那么如果某位同学手中的数字的所有正约数之和等于w,那么这位同学就是你的小伙伴。

输入格式

输入包含n组数据(最多100组)
对于每组测试数据,输入只有一个数字w。

输出格式

对于每组数据输出两行,第一行包含一个整数m,表示有m(如果m = 0,只输出一行0即可)个伙伴。
第二行包含相应的m个数,表示伙伴的数字。注意:小伙伴的数字必须按照升序排列。

样例

\(input\)


\(output\)


思路

简单的数论

暴力算法(显然会TLE)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
#define rint register int
using namespace std;
inline int read(){
	int x = 0, f = 1; char ch = getchar();
	for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
	for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
	return x * f;
}
const int maxn = 1e8 + 50;

int a[maxn], cnt, col[maxn];
bool flag;

inline void solve(int x,int k){
	col[x] = 0;
	int sol = sqrt(x);
	for(rint i = 1; i <= sol; i++)
		if(x % i == 0){ col[x] += i;col[x] += x/i;}
	if(sol * sol == x){ col[x] -= sol;}
	if(col[x] == k){ flag = true;a[++cnt] = x;}
	col[x] = col[x];
	return;
}

int main(){
	//freopen("a.in", "r", stdin);
	int n;
	while(~scanf("%d", &n)){
		cnt = 0, flag = 0;
		for(rint i = 1; i <= n; ++i){
			if(col[i] != n && col[i]) continue;
			else solve(i, n);
		}
		if(flag == false){
			printf("0\n");
			continue;
		}
		printf("%d\n", cnt);
		for(rint i = 1; i < cnt; ++i){
			printf("%d ", a[i]);
		}
		printf("%d\n", a[cnt]);
	}
	return 0;
}

正解算法

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e4;
bool is_not_prime[maxn];
int prime[maxn];
int m;
void primes(int n) {
    is_not_prime[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!is_not_prime[i])
            prime[++prime[0]] = i;
        for (int j = 1; i * prime[j] <= n; j++) {
            is_not_prime[i * prime[j]] = 1;
            if (i % prime[j] == 0)
                break;
        }
    }
}
bool is_prime(int x) {
    if (x <= maxn)
        return !is_not_prime[x];
    for (int i = 1; prime[i] * prime[i] <= x; i++) {
        if (x % prime[i] == 0)
            return 0;
    }
    return 1;
}
int a[maxn], cnt;
void dfs(int now, int p, int x) {
    if (now == 1) {
        a[++cnt] = x;
        return;
    }
    if (is_prime(now - 1) && now > prime[p])
        a[++cnt] = x * (now - 1);
    for (int i = p; prime[i] * prime[i] <= now; i++) {
        int pi = prime[i];
        int sum = prime[i] + 1;
        for (; sum <= now; pi *= prime[i], sum += pi) {
            if (now % sum == 0)
                dfs(now / sum, i + 1, x * pi);
        }
    }
}
int main() {
    primes(maxn);
    while (scanf("%d", &m) != EOF) {
        cnt = 0;
        dfs(m, 1, 1);
        printf("%d\n", cnt);
        sort(a + 1, a + cnt + 1);
        for (int i = 1; i <= cnt; i++) {
            printf("%d ", a[i]);
        }
        printf("\n");
    }
}

D.string

题目描述

给定一个由小写字母组成的字符串 s。有 m 次操作,每次操作给定 3 个参数 l,r,x。如果 x=1,将 s[l]∼s[r] 升序排序;如果 x=0,将 s[l] s[r] 降序排序。你需要求出最终序列。

输入格式

第一行两个整数 n,m,表示字符串长度为n,有m次操作。
第二行一个字符串s。
接下来m行每行三个整数 l,r,x。

输出格式

一行一个字符串表示答案。

样例

\(input\)

5 2
cabcd
1 3 1
3 5 0

\(output\)

abdcc

思路

根据v数据范围可以看出来单纯的sort肯定解决不了问题,再想一下,英文字母只有26个,那么这么大的序列里面肯定会有很多一样的字符,看数据范围和操作的大概样式,可以联想到线段树,要是sort的话可以把很多个字符一起sort,可以大大提高效率。

代码实现



#include <cstdio>
#include <cstring>
#define lson (p * 2)
#define rson (p * 2 + 1)
#define mid ((l + r >> 1))
using namespace std;
char s[100005];
int n, m, l, r, x, tree[400005], cnt[30];
void build(int p, int l, int r) {
	if (l == r) return tree[p] = s[l] - 'a' + 1, void();
	build(lson, l, mid), build(rson, mid + 1, r);
	tree[p] = (tree[lson] == tree[rson]) ? tree[lson] : 0;
}
void query(int p, int l, int r, int s, int t) {
	if (s <= l && r <= t && tree[p]) return cnt[tree[p]] += r - l + 1, void();
	if (tree[p] && l != r) tree[lson] = tree[p], tree[rson] = tree[p];
	if (s <= mid) query(lson, l, mid, s, t);
	if (t > mid) query(rson, mid + 1, r, s, t);
}
void update(int p, int l, int r, int s, int t, int v) {
	if (s <= l && r <= t || tree[p] == v) return tree[p] = v, void();
	if (tree[p] && l != r) tree[lson] = tree[p], tree[rson] = tree[p];
	if (s <= mid) update(lson, l, mid, s, t, v);
	if (t > mid) update(rson, mid + 1, r, s, t, v);
	tree[p] = (tree[lson] == tree[rson]) ? tree[lson] : 0;
}
void print(int p, int l, int r) {
	if (l == r || tree[p]) for (int i = l; i <= r; i++) putchar(tree[p] + 'a' - 1);
	else print(lson, l, mid), print(rson, mid + 1, r);
}
int main() {
	scanf("%d%d%s", &n, &m, s + 1);
	build(1, 1, n);
	for (int i = 1; i <= m; i++) {
		scanf("%d%d%d", &l, &r, &x);
		memset(cnt, 0, sizeof(cnt));
		query(1, 1, n, l, r);
		if (x) {
			for (int i = 1; i <= 26; i++) 
				if (cnt[i]) update(1, 1, n, l, l + cnt[i] - 1, i), l = l + cnt[i];
		} else {
			for (int i = 26; i >= 1; i--) 
				if (cnt[i]) update(1, 1, n, l, l + cnt[i] - 1, i), l = l + cnt[i];
		}
	}
	print(1, 1, n);
	return 0;
}
posted @ 2020-08-02 12:01  Blancа  阅读(186)  评论(0编辑  收藏  举报
哈哈,你打开了控制台,是想要看看我的秘密吗?
莫挨老子!