2021牛客寒假算法基础集训营6题解

本场题目很友好, H H H题还放过了暴力,不过感觉时限开的有点大了。

A、 回文括号序列计数

题意:
求长度为 n n n的回文括号序列数。共 T T T组数据。
数据范围: 1 ≤ n ≤ 1 0 9 , 1 ≤ T ≤ 1 0 6 1\leq n\leq 10^9,1\leq T\leq 10^6 1n109,1T106

题解:
括号序列一直都是混淆回文串的一把好手(握拳)。
看到这个数据范围: O ( log ⁡ n ) O(\log n) O(logn)也很难过,那么极大可能是 O ( 1 ) O(1) O(1)的。
n n n为奇数此时已经不满足回文串的要求了。
考虑括号序列的第一个字符一定是左括号,最后一个字符一定是右括号,因此不可能存在非空的括号序列。因此只有空串是回文括号序列。

代码:

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

typedef long long ll;
int n;

void solve() {
    
    
	scanf("%d", &n);
	printf("%d\n", !n);
}

int main()
{
    
    
	int T = 1;
	scanf("%d", &T);
	for(int i = 1; i <= T; ++i) solve();
	return 0;
}

B、系数

题意:
( x 2 + x + 1 ) n (x^2+x+1)^n (x2+x+1)n x k x^k xk的系数模 3 3 3的结果,
数据范围: 0 ≤ n ≤ 1 0 15 , 0 ≤ k ≤ 2 n 0\leq n\leq 10^{15}, 0\leq k\leq 2n 0n1015,0k2n

题解:
一个与题目联系十分紧密的转换:
在模 3 3 3意义下有:
( x 2 + x + 1 ) n = ( x 2 − 2 x + 1 + 3 x ) n = ( x 2 − 2 x + 1 ) n = ( x − 1 ) 2 n (x^2+x+1)^n=(x^2-2x+1+3x)^n=(x^2-2x+1)^n=(x-1)^{2n} (x2+x+1)n=(x22x+1+3x)n=(x22x+1)n=(x1)2n
由于模数很小,而 n , k n,k n,k很大,所以直接使用 L u c a s Lucas Lucas即可。

代码:

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

typedef long long ll;
const int mod = 3;
ll n, k; 

ll C(ll a, ll b, ll p) {
    
    
	if(a < b) return 0;
	if(a == b) return 1;
	
	ll ta = 1, tb = 1;
	for(ll i = 1, j = a; i <= b; ++i, --j)
		ta = ta * j % p, tb = tb * i % p;
		
	//tb的mod - 2在此就是tb 
	return ta * tb % p;
}

ll Lucas(ll a, ll b, ll p) {
    
    
	if(a < b) return 0;
	if(a < p && b < p) return C(a, b, p);
	return C(a % p, b % p, p) * Lucas(a / p, b / p, p) % p;
}

void solve() {
    
    
	scanf("%lld%lld", &n, &k);
	ll f = (2 * n - k) & 1;
	ll res = Lucas(2 * n, k, mod);
	if(f == 1) res = (mod - res) % mod;
	printf("%lld\n", res);
}

int main()
{
    
    
	int T = 1;
	scanf("%d", &T);
	for(int i = 1; i <= T; ++i) solve();

	return 0;
}

C、末三位

题意:
5 n 5^n 5n的最后三位,不足三位补 0 0 0。共 T T T组数据。
数据范围: 1 ≤ n ≤ 1 0 9 , 1 ≤ T ≤ 1 0 6 1\leq n\leq 10^9,1\leq T\leq 10^6 1n109,1T106

题解:
赛时没有细想,直接上快速幂了。时间复杂度为: O ( T log ⁡ n ) O(T\log n) O(Tlogn)
由于 5 5 5比较特殊,所以打个表可以发现末三位的循环节为 4 4 4
这类题大致都可以打表做。
代码:

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

typedef long long ll;
int n;

const int mod = 1000;

int qp(int a, int b) {
    
    
	int ans = 1;
	while(b) {
    
    
		if(b & 1) ans = (ll)ans * a % mod;
		a = (ll)a * a % mod;
		b >>= 1;
	}	
	return ans;
}

void solve() {
    
    
	int ans = qp(5, n);
	printf("%03d\n", ans);
}

int main()
{
    
    
	while(~scanf("%d", &n)) solve();

	return 0;
}

D、划数

题意: 给定 n n n个数,第 i i i个数为 a i a_i ai。每次取出两个数相加对 11 11 11取模成为新的数。最后操作剩两个数时,有一个 x x x,一个 y y y,先已知 y y y,且保证 y ≥ 11 y\geq 11 y11,求 x x x。共 T T T组。
数据范围: 1 ≤ n ≤ 150000 , 1 ≤ T ≤ 10 , 1 ≤ a i ≤ 1 0 6 1\leq n\leq 150000,1\leq T\leq 10,1\leq a_i\leq 10^6 1n150000,1T10,1ai106

题解:
本题比较坑的点是 n = 2 n=2 n=2时, x x x y y y都可能大于等于 11 11 11,否则 x x x必定是取模 11 11 11后的数。特判 n = 2 n=2 n=2即可。

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod = 11;

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

void solve() {
    
    
	
	int sum = 0;
	for(int i = 1, x; i <= n; ++i) scanf("%d", &a[i]);
	if(n == 2) {
    
    
		if(a[1] == cnt) printf("%d\n", a[2]);
		else printf("%d\n", a[1]);
		return ;
	}
	
	for(int i = 1; i <= n; ++i) sum = (sum + a[i]) % mod;
	int res = ((sum - cnt) % 11 + 11) % 11;
	printf("%d\n", res);
}
 

int main()
{
    
    
	while(~scanf("%d%d", &n, &cnt)) solve();
}

E、网格

题意:
给定一个 n × m n\times m n×m的网格,第 i i i j j j列上有数字 a i , j a_{i,j} ai,j,每个位置需要向四个方向中取两个方向,且一个格子的两个方向必须垂直。定义 w ( x ) = x + b i t _ c n t ( x ) w(x)=x+bit\_cnt(x) w(x)=x+bit_cnt(x)
b i t _ c n t ( x ) bit\_cnt(x) bit_cnt(x) x x x的二进制中 1 1 1的个数。
如果两个相邻位置(即 ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ = 1 |x_1-x_2|+|y_1-y_2|=1 x1x2+y1y2=1)互相选择对方的方向,则对答案产生 w ( a x 1 , y 1 ⊕ a x 2 , y 2 ) w(a_{x_1,y_1}\oplus a_{x_2,y_2}) w(ax1,y1ax2,y2)的贡献。
现在问答案最大是多少。
数据范围: 1 ≤ n , m ≤ 1000 , 1 ≤ a i , j ≤ 1024 1\leq n,m\leq 1000,1\leq a_{i,j}\leq 1024 1n,m1000,1ai,j1024

题解:
由于一个格子的两个方向垂直,所以整体来看横向和竖向对答案的贡献是独立的。所以我们只需要单独考虑每个方向即可。
现在考虑一行:
f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1]表示第 j j j个格子选择了向左/向右这个方向。
所以状态转移为:
f [ i ] [ j ] [ 0 ] = m a x ( f [ i ] [ j − 1 ] [ 0 ] , f [ i ] [ j − 1 ] [ 1 ] + w ( a [ i − 1 ] [ j ] ⊕ a [ i ] [ j ] ) ) f[i][j][0]=max(f[i][j-1][0],f[i][j-1][1]+w(a[i-1][j]\oplus a[i][j])) f[i][j][0]=max(f[i][j1][0],f[i][j1][1]+w(a[i1][j]a[i][j]))
f [ i ] [ j ] [ 1 ] = m a x ( f [ i ] [ j − 1 ] [ 0 ] , f [ i ] [ j − 1 ] [ 1 ] ) f[i][j][1]=max(f[i][j-1][0],f[i][j-1][1]) f[i][j][1]=max(f[i][j1][0],f[i][j1][1])

列同行。

代码:

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

typedef long long ll;
const int N = 1010;
int a[N][N];
ll f[N][2];
int n, m;

int cnt[1024];
int w(int x) {
    
    
	if(~cnt[x]) return cnt[x];
	int act = 0;
	int tx = x;
	while(tx) ++act, tx -= (tx & -tx);
	return cnt[x] = act + x;
}

int main()
{
    
    
	memset(cnt, -1, sizeof cnt);
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= m; ++j)
			scanf("%d", &a[i][j]);	
	
	ll res = 0; 
	for(int i = 1; i <= n; ++i) {
    
    
		for(int j = 2; j <= m; ++j) {
    
    
			f[j][0] = max(f[j - 1][0], f[j - 1][1] + w(a[i][j - 1] ^ a[i][j]));
			f[j][1] = max(f[j - 1][0], f[j - 1][1]);
		}
		res += max(f[m][0], f[m][1]);
	} 
	
	for(int j = 1; j <= m; ++j) {
    
     
		for(int i = 2; i <= n; ++i) {
    
    
			f[i][0] = max(f[i - 1][0], f[i - 1][1] + w(a[i - 1][j] ^ a[i][j]));
			f[i][1] = max(f[i - 1][0], f[i - 1][1]); 
		} 
		res += max(f[n][0], f[n][1]);
	} 
	
	printf("%lld\n", res);
	return 0;
}

F、组合数问题

题意:
∑ n i ( i % 4 = = 0 ) × C n i \sum_n^i (i\%4==0)\times C_n^i ni(i%4==0)×Cni
数据范围: 1 ≤ n ≤ 1 0 18 1\leq n\leq 10^{18} 1n1018,保证 n n n 4 4 4的倍数。

题解:
解法一:
赛时是打表做的,可以考虑先取模打表。

n n n r e s 1 res1 res1 r e s 2 res2 res2 差 值 差值
004 6 2 -4
008 56 72 16
012 1056 992 -64
016 16256 16512 256
020 262656 261632 -1024
024 4192256 4196352 4096
028 67117056 67100672 -16384
032 1073709056 1073774592 65536

其中 r e s 2 res2 res2是我们要求的,那么可以发现规律了。
这里 k = n 4 k=\frac{n}{4} k=4n,那么当 k k k为奇数时,差值为负。
由于 r e s 1 + r e s 2 = ∑ n i ( i % 2 = = 0 ) × C n i = 2 n − 1 res1+res2=\sum_n^i(i\%2==0)\times C_n^i=2^{n-1} res1+res2=ni(i%2==0)×Cni=2n1
现在得知 r e s 2 − r e s 1 res2-res1 res2res1的值,所以可以直接求得 r e s 1 res1 res1

证明一下:这里 n n n为偶数。
∑ n i ( i % 2 = = 0 ) × C n i = 2 n − 1 \sum_n^i(i\%2==0)\times C_n^i=2^{n-1} ni(i%2==0)×Cni=2n1
这里有: ( x + y ) n = C n 0 x 0 y n + C n 1 x y n − 1 + . . . + C n n − 1 x n − 1 y + C n n x n y 0 (x+y)^n=C_n^0 x^0y^n+C_n^1xy^{n-1}+...+C_n^{n-1}x^{n-1}y+C_n^nx^ny^0 (x+y)n=Cn0x0yn+Cn1xyn1+...+Cnn1xn1y+Cnnxny0
x = 1 , y = 1 x=1,y=1 x=1,y=1, 该二项式值为 C n 0 + C n 1 + . . . + C n n − 1 + C n n = 2 n C_n^0+C_n^1+...+C_n^{n-1}+C_n^n=2^n Cn0+Cn1+...+Cnn1+Cnn=2n
x = 1 , y = − 1 x=1,y=-1 x=1,y=1,该二项式值为 C n 0 − C n 1 + . . . − C n n − 1 + C n n = 0 C_n^0-C_n^1+...-C_n^{n-1}+C_n^n=0 Cn0Cn1+...Cnn1+Cnn=0
两式相加: 2 ( C n 0 + C n 2 + . . . + C n n ) = 2 n 2(C_n^0+C_n^2+...+C_n^n)=2^n 2(Cn0+Cn2+...+Cnn)=2n,所以得证。

解法二:来自官方题解
大学以来不碰高中数学真是想不到。

考虑复数:
i 1 = i i^1=i i1=i
i 2 = − 1 i^2=-1 i2=1
i 3 = − i i^3=-i i3=i
i 4 = 1 i^4=1 i4=1

这里有: ( x + y ) n = C n 0 x 0 y n + C n 1 x y n − 1 + . . . + C n n − 1 x n − 1 y + C n n x n y 0 (x+y)^n=C_n^0 x^0y^n+C_n^1xy^{n-1}+...+C_n^{n-1}x^{n-1}y+C_n^nx^ny^0 (x+y)n=Cn0x0yn+Cn1xyn1+...+Cnn1xn1y+Cnnxny0
x = 1 , y = i x=1,y=i x=1,y=i,该二项式为:
( 1 + i ) n = C n 0 i n + C n 1 i n − 1 + C n 2 i n − 2 + C n 3 i n − 3 + . . . + C n n − 1 i + C n n i 0 (1+i)^n=C_n^0 i^n+C_n^1i^{n-1}+C_n^2i^{n-2}+C_n^3i^{n-3}+...+C_n^{n-1} i+C_n^ni^0 (1+i)n=Cn0in+Cn1in1+Cn2in2+Cn3in3+...+Cnn1i+Cnni0

x = 1 , y = − i x=1,y=-i x=1,y=i,该二项式为:
( 1 − i ) n = C n 0 ( − i ) n + C n 1 ( − i ) n − 1 + . . . + C n n − 1 ( − i ) + C n n ( − i ) 0 (1-i)^n=C_n^0(-i)^n+C_n^1(-i)^{n-1}+...+C_n^{n-1}(-i)+C_n^n (-i)^0 (1i)n=Cn0(i)n+Cn1(i)n1+...+Cnn1(i)+Cnn(i)0
= C n 0 i n − C n 1 i n − 1 + C n 2 i n − 2 − C n 3 i n − 3 + . . . − C n n − 1 i + C n n i 0 =C_n^0 i^n-C_n^1 i^{n-1}+C_n^2i^{n-2}-C_n^3i^{n-3}+...-C_n^{n-1}i+C_n^ni^0 =Cn0inCn1in1+Cn2in2Cn3in3+...Cnn1i+Cnni0

( 1 + i ) n + ( 1 − i ) n = 2 ( C n 0 − C n 2 + C n 4 − C n 6 . . . + C n n ) (1+i)^n+(1-i)^n=2(C_n^0-C_n^2+C_n^4-C_n^6...+C_n^n) (1+i)n+(1i)n=2(Cn0Cn2+Cn4Cn6...+Cnn)
( 1 + 1 ) n + ( 1 − 1 ) n = 2 ( C n 0 + C n 2 + C n 4 + C n 6 . . . + C n n ) (1 +1)^n+(1-1)^n=2(C_n^0+C_n^2+C_n^4+C_n^6...+C_n^n) (1+1)n+(11)n=2(Cn0+Cn2+Cn4+Cn6...+Cnn)
所以 ∑ n i ( i % 4 = = 0 ) × C n i = ( 1 + i ) n + ( 1 − i ) n + 2 n 4 \sum_n^i (i\%4==0)\times C_n^i=\frac{(1+i)^n+(1-i)^n+2^n}{4} ni(i%4==0)×Cni=4(1+i)n+(1i)n+2n
套用一下复数快速幂即可。

代码:
解法一:

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

typedef long long ll;

const int mod = 998244353;
ll n;
ll qp(ll a, ll b) {
    
    
	ll ans = 1;
	while(b) {
    
    
		if(b & 1) ans = (ll)ans * a % mod;
		a = (ll)a * a % mod;
		b >>= 1;
	}
	return ans;
}

void solve() {
    
    
	scanf("%lld", &n);
	ll ano = n / 4;
	ll cha = qp(4, ano);
	cha = cha * qp(2, mod - 2) % mod; 
	if(ano & 1) cha *= -1;
	ll res = ((qp(2, n - 2) + cha) % mod + mod) % mod;
	printf("%lld\n", res);
}

int main()
{
    
    
	int T = 1;
	//scanf("%d", &T);
	for(int i = 1; i <= T; ++i) solve();

	return 0;
}


解法二:

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

typedef long long ll;

const int mod = 998244353;

typedef long long ll;
struct Complex {
    
    
	ll a, b; // a + bi
	Complex(ll _a, ll _b) {
    
    
		a = _a, b = _b;
	}
};

Complex mul(Complex A, Complex B) {
    
    
	ll a = ((A.a * B.a - A.b * B.b) % mod + mod) % mod;
	ll b = ((A.a * B.b + A.b * B.a) % mod + mod) % mod;
	return Complex(a, b); 
} 

Complex C_qp(Complex A, ll b) {
    
    
	Complex ans(1, 0);
	while(b) {
    
    
		if(b & 1) ans = mul(ans, A);
		A = mul(A, A);
		b >>= 1;
	}
	return ans;
}

ll qp(ll a, ll b) {
    
    
	ll ans = 1;
	while(b) {
    
    
		if(b & 1) ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}

ll n;
int main()
{
    
    
	scanf("%lld", &n);
	ll res = qp(2, n);
	
	Complex A(1, 1);
	Complex B(1, -1);
	A = C_qp(A, n);
	B = C_qp(B, n);
	
	res = (res + A.a + B.a) % mod;
	res = res * qp(4, mod - 2) % mod;
	res = (res + mod) % mod;
	
	printf("%lld\n", res);
	
	return 0;
}

G、机器人

题意:
给定 n n n a , b a,b a,b,初始有一个 x x x,求一个指定的 n n n a , b a,b a,b排列使得最后 a x + b ax+b ax+b最大,前一次的 a i − 1 x + b i − 1 a_{i-1}x+b_{i-1} ai1x+bi1作为后一次 a i x + b i a_ix+b_i aix+bi x x x
数据范围: 1 ≤ n , x , a i , b i ≤ 20 1\leq n,x,a_i,b_i\leq 20 1n,x,ai,bi20

题解:
解法一:
贪心策略:类似于 n o i p 2012 noip2012 noip2012提高的国王游戏贪心策略。
考虑现在已经是使得 x x x最大的 a , b a,b a,b排列。
那么一定有 a i + 1 ( a i x + b i ) + b i + 1 > a i ( a i + 1 x + b i + 1 ) + b i a_{i+1}(a_ix+bi)+b_{i+1}>a_i(a_{i+1}x+b_{i+1})+b_i ai+1(aix+bi)+bi+1>ai(ai+1x+bi+1)+bi
否则对于第 i + 2 i+2 i+2及其之后的操作对应的 x x x都更小。
化简一下得到: a i + 1 b i + b i + 1 > a i b i + 1 + b i a_{i+1}b_i+b_{i+1}>aib_{i+1}+b_i ai+1bi+bi+1>aibi+1+bi
所以整体按照 a i + 1 b i + b i + 1 a_{i+1}b_i+b_{i+1} ai+1bi+bi+1从小到大排序即可。

解法二:
数据范围直接提示是状压 d p dp dp
考虑状压的转移: d p [ i ] → d p [ i + ( 2 j ) ] dp[i]\to dp[i+(2^j)] dp[i]dp[i+(2j)]其中 i & 2 j = 0 i \& 2^j=0 i&2j=0
那么具体转移方程为: d p [ i + ( 2 j ) ] = a j × d p [ i ] + b j dp[i+(2^j)]=a_j\times dp[i]+b_j dp[i+(2j)]=aj×dp[i]+bj

代码:
本题考虑到极端是 2 0 20 20^{20} 2020,会爆 l o n g   l o n g long\ long long long,可以考虑用 _ _ i n t 128 \_\_int128 __int128或者高精度。

解法一:

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

#define ptsd puts("") 
#define ll __int128
ll read() {
    
    
    ll x = 0, f = 1;
    char ch = getchar();
    while(!isdigit(ch)) {
    
    
        if(ch == '-') f = -1;
        ch = getchar();
    }

    while(isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
    return x * f;
} 

void output(ll x) {
    
    
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) output(x / 10);
    putchar(x % 10 + '0');
} 

int n;
struct Node {
    
    
	int x, y;
	bool operator < (const Node &A) const {
    
    
		return x * A.y + y < A.x * y + A.y;
	}
}node[30];


int main()
{
    
    
	ll res = 0;
	scanf("%d%d", &n, &res);
	for(int i = 1; i <= n; ++i) scanf("%d%d", &node[i].x, &node[i].y);
	sort(node + 1, node + n + 1);
	for(int i = 1; i <= n; ++i) res = res * node[i].x + node[i].y;
	output(res); ptsd;
}

解法二:

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

#define ll __int128 
#define ptsd putchar('\n')

void output(ll x) {
    
    if(x > 9) output(x / 10); putchar(x % 10 + '0'); }
void out(ll x) {
    
    if(x < 0) putchar('-'), x = -x; output(x);} 

struct Node {
    
    
	int a, b;
}node[20];
int n, x;

ll f[1 << 20];

int main()
{
    
    
	scanf("%d%d", &n, &x);
	for(int i = 0; i < n; ++i) scanf("%d%d", &node[i].a, &node[i].b); 
	f[0] = x;
	int last = (1 << n) - 1;
	for(int i = 0; i < last; ++i) //last时已经是全1,不会再转移,所以枚举到(1<<n)-1即可
		for(int j = 0; j < n; ++j) 
			if(!(i >> j & 1)) 
				f[i + (1 << j)] = max(f[i + (1 << j)], node[j].a * f[i] + node[j].b);
		
	output(f[last]); ptsd;
	return 0;
}

H、动态最小生成树

题意:
初始给定一个 n n n m m m边的无向图,每条边都有边权,给定 q q q个操作。
有两种操作:
操作 1 1 1是修改第 x x x条边为连接点为 y , z y,z y,z,边权为 t t t
操作 2 2 2是查询只用编号在 [ l , r ] [l,r] [l,r]内边构造的最小生成树权值,如果不能构造输出 I m p o s s i b l e Impossible Impossible
数据范围:
1 ≤ n ≤ 200 , 1 ≤ m ≤ 30000 , 1 ≤ q ≤ 30000 1\leq n\leq 200, 1\leq m\leq 30000, 1\leq q\leq 30000 1n200,1m30000,1q30000
1 ≤ u i , v i ≤ n , 1 ≤ w i ≤ 10000 1\leq u_i,v_i\leq n,1\leq w_i\leq 10000 1ui,vin,1wi10000
1 ≤ x ≤ m , 1 ≤ y , z ≤ n , 1 ≤ t ≤ 10000 1\leq x\leq m,1\leq y,z\leq n,1\leq t\leq 10000 1xm,1y,zn,1t10000
题解:
本题时限过大或者说数据范围较小,暴力能过。正解为线段树维护最小生成树边。

I、贪吃蛇

b f s bfs bfs模板

J、天空之城

m s t mst mst模板

猜你喜欢

转载自blog.csdn.net/weixin_43900869/article/details/114070162
今日推荐