北京理工大学20级5.1训练总结&&题解整理

预计难度:
IK-JO-DGL-FH-CEMN-AB

实际难度:
I-JKL-DF-CGN-EHMO-AB

比赛链接

GHKO过得人比我想的要少(尤其是O,可能大家没怎么做过交互?),CF过得人比我想的要多。

题解

除了去百度,gym比赛的题解还可以大概率在原比赛的右下角找到,例如对于本场比赛A题,点击题目编号进入到gym网站,可以在右下角看到:

tutorial就是题解,如果没有tutorial,点announcement里面也可能有放题解。(可能需要梯子)

百度和gym里都没有题解,还可以问别的大佬或者我(欢迎)。

A

可以看这篇

大体思路是,(dp[i])表示以(i)结尾的子序列中所求值最大是多少,所以可以得到一个遍历(dp[1]~dp[i-1])来更新的(n^2)的DP,但事实上注意到如果存在(j>k)(a[j]&a[k] != 0),则用(dp[j])是优于(dp[k])的,所以只需要记录每个二进制位最靠右出现的位置,这样每次转移就只需要“(a[i])的二进制位数”次。

B

可以在gym右下角找到题解,放一下我在群里的解释:

“就是考虑n中数字对应字符串中数字的情况,其实只用考虑三种:1、n整个就是字符串中某个数字;2、n的一个子串是原串中完整的一个数字;3、n是由一个字符串的后缀和一个字符串的前缀组成的;”

“情况2可以18*18的枚举n中哪个子串是完整的一个数字,然后补全前后的数字,和n去作比较”

“情况3可以枚举一个前缀,把前缀+1后和n做匹配,尽可能多的匹配,中间补上一些数(比如999231000,枚举999为前缀,+1得到1000,匹配最后的1000,因此最早出现是在230999231000处)”

C

这篇不错。

看了大家的代码和这个题解才知道这题我们当时做麻烦了,原来这么简单的做就可以了,难怪我给这题难度估高了(

D

浙江省赛的C题,我的方法是求出(8*7/2=28)条边的边长,从小到大排序,看是不是有12个(x),12个(sqrt{2} x),4个(sqrt{3} x)这样的形式。

E

浙江省赛的F题,链接同上。

F

浙江省赛的J题,链接同上(这题过得人也比预想多,说明大家基础知识还是挺扎实的)。

G

我最近做到的题中很喜欢的一个思维题,还挺烧脑的(虽然出题人英文水平似乎令人发指)。

#include <stdio.h>

void solve(){
	int n,m;
	scanf("%d%d",&n,&m);
	if(m==1){
		printf("%d
",n+1);
		return;
	}
	if(m==n){
		puts("2");
		return;
	}
	puts("3");
	return;
}

int main(){
	int T; scanf("%d",&T);
	while(T--) solve();
}

H

我觉得是个读完题看完样例解释就可以做的题,结果基本没人过,可能题面不好懂?

首先,注意到(k>=3)的时候答案一定是(1),因为通过像样例2那样走(3→2→3)的方式,我们可以让一条路径长度增加若干个(2),可以发现,通过这种走法,对于(k>=3)(k),总可以走出一种路径长度不是(k)的方案,所以此时答案只能是1。

然后,(k=1)的时候答案是(n),毕竟不管怎么走路径长都是(1)的倍数(

(k=2)时,可以发现重点是你选的halting state之间的距离必须是(2)的倍数,所以对整个图进行染色(即随便选个点染黑,相邻点染白,使每个点相邻点颜色都和自己不一样),答案就是白点数和黑点数的较大值。但也有染色失败的情况(比如1-2,2-3,3-1组成的一个环),这种情况答案依然只能是1。(这就是二分图的知识,您们之后会学,但这题其实也用不太到)

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,a,n) for(int i=n;i>=a;i--)
#define pb push_back
#define SZ(v) ((int)v.size())
typedef long long ll;
typedef double db;
typedef pair<int,int> pii;
int n,m,u,v,k,clr[200010];
vector<int> g[200010];
void dfs(int now,int c){
	clr[now]=c;
	for(auto to:g[now]){
		if(clr[to]==-1){
			dfs(to,1-c);
		}
	}
}
int main(){
	cin>>n>>m>>k;
	rep(i,1,m){
		scanf("%d%d",&u,&v);
		g[u].pb(v);
		g[v].pb(u);
	}
	if(k==1){
		printf("%d
",n);
		return 0;
	}
	if(k>2){
		printf("%d
",1);
		return 0;
	}
	memset(clr,-1,sizeof(clr));
	dfs(1,0);
//	rep(i,1,n)	printf("%d
",clr[i]);
	int cnt=0;
	rep(i,1,n){
		cnt+=clr[i];
		for(auto to:g[i]){
			if(clr[to]==clr[i]){
				puts("1");
				return 0;
			}
		}
	}
	printf("%d
",max(cnt,n-cnt));
	return 0;
}

I

本来没这题的,签到只有个K,想了想一道签到不够用,加了这道(结果K表现得并不签到,幸亏加了这个签到)。

J

一个考验分类讨论能力的题,写的优雅的话就是一个式子,不优雅的话写一些if-else也可以做,18/98的结果可以看出目的达到了。

K

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a,b;
int main()
{
	cin>>a>>b;
	if(a==2&&b<=3)cout<<"Yes";
	else cout<<"No";
}

(2k)个数里有(k)个是偶数(假设没2),所以他们都不是质数,所以质数一定不会超过(k)个,然后对于有2的情况,手写一下看看,发现也只有那么几个情况可能是YES。

L

按照题意去写就行,大家还都比较会写,不多说了。

M

来自gym右下角的题解,题里提到了两种维护方法,主要讲的是用朴素线段树来维护的,不过里面提到的吉司机树(SGTB)也可以了解一下,用SGTB来维护的话就不咋用动脑,很板子了。

N

浙江省赛的G题,题解也在上上上面。

其实思路也比较简单,每个联通块我用一个并查集维护,只不过并查集除了老大还记录了这个块的大小,然后新加入一个块时暴力判断这个块周围有几个位置已经有块了来决定加入这个块给联通块周长带来了多少贡献。

/*
 * @date:
 * @source:
*/
#include <bits/stdc++.h>

using namespace std;

const int MAXN = 500000 + 5;

int N;
map<pair<int, int>, int> Map;
int Fa[MAXN], Siz[MAXN];

int findFa(int x) {
    return Fa[x] == x ? x : Fa[x] = findFa(Fa[x]);
}

void merge(int a, int b) {
    a = findFa(a), b = findFa(b);
    if (a == b) return ;
    Fa[a] = b;
    Siz[b] += Siz[a];
}

const int Dx[] = {0, 0, 1, -1, -1, 1};
const int Dy[] = {1, -1, 0, 0, 1, -1};

vector<int> v;

int main() {
    scanf("%d", &N);
    int cnt = 0;
    for (int i = 1, opt, x, y; i <= N; ++i) {
        scanf("%d%d%d", &opt, &x, &y);
        if (opt == 1) {
            if (Map.find({x, y}) == Map.end()) {
                Map[ {x, y}] = ++cnt;
                Fa[Map.size()] = cnt;
            }
            int num = 0;
            for (int i = 0; i < 6; ++i) {
                int nx = x + Dx[i], ny = y + Dy[i];
                if (Map.find({nx, ny}) != Map.end()) {
                    merge(Map[ {x, y}], Map[ {nx, ny}]);
                    ++num;
                }
            }
            Siz[findFa(Map[ {x, y}])] += 6 - 2 * num;
        } else {
            printf("%d
", Siz[findFa(Map[ {x, y}])]);
        }
    }
    return 0;
}

O

可以看出,其实只要能确定知道一个数在A中的位置和在B中的位置,就可以知道(k)了。那么,因为询问是问max,那么比较直接的一个思路就是,确定最大数(n)在A和B中的位置即可。

这里借用一下刘子政同学的思路:

“先询问(0,0~n-1),如果结果都一样且等于n,则A[0]保证是最大值,然后询问(1,0~n-1)找B的最大值就可以了;”

“如果不一样的话,那么询问的结果里面会出现一个最大值,那个就是B[i]max的位置了”

“然后反过来询问(0~n-1,k)(k与B的最大值位置不同)就可以了”

原文地址:https://www.cnblogs.com/fried-chicken/p/14725632.html