Logo FiraCode 的博客

博客

标签
暂无

CF1438E

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-04-27 20:04:01

题解思路:

我们会发现一个性质:满足条件的区间不会很多。

因为满足条件的数很少,所以我们就可以用暴力。

我们先假设,这个区间是以 $a_l$ 为主导的,就是 $a_l$ 是 $a_l$ 和 $a_r$ 里二进制最高的一位。

对于这样的情况下,那么我们就暴力求满足条件的区间就可以了。

先对于 $l$ 枚举 $r$。

终止条件是什么呢?

我们假设最高位是第 k 位,那么 $\sum^{n}_{i = 1}a_i$ 一定要 $\le 2^{k + 1}$,首先我们最大值在第 k 位上,而异或又是不进位加法,所以这个区间的和就是一定要 $\le 2^{k + 1}$ 的。

所以我们就暴力枚举 $r$,当遇到不符合这个条件的 $r$ 时,就终止就可以了。

然后再倒着,对于 $r$ 枚举 $l$,就可以了。

证明时间复杂度:

假设这是以第 $k$ 位二进制,那么最多有 $\log n$ 位二进制。对于每个二进制的情况下呢,我们去讨论。

我们假设二进制最高位为 $k$,的下标有: $p_1 , p_2 ... ... p_n$,假设 $l = p_1$,那么当 $r = p_3$ 时一定会终止,因为要是 $l = p_1 , r = p_3$ 时,累加和就是:$p_2 + p_3$ 而这个数一定会超过 $2^{k + 1}$。

那么对于每个下标累加和是多少: $\sum^{x}{i = 3} p_i - p{i - 2}$

那么这些数的累加和算下来他就是 $p_x + p_{x - 1} - p_1 - p_2 \le 2n$,就是一个 $O(n)$ 级别的。

有 $\log n$ 次操作,,每次 $O(n)$ 总时间复杂度为 $O(n \log n)$

AC CODE:

#include <iostream> 
#include <algorithm>
#include <vector>
#include <cstring>
#include <cstdio>
#include <set>
using namespace std;
typedef long long ll;
const int N = 200010;
int n;
int a[N];
set <pair <int , int>> s;\/\/用set去重
int log2 (int x) {\/\/求log2 
	int cnt = 0;
	while (x) {
		++ cnt;
		x >>= 1;
	}
	return cnt;
}
void solve (bool op) {\/\/0代表正着,1代表倒着 
	vector <int> pre (n + 10);
	for (int i = 1; i <= n; ++ i)
		pre[i] = pre[i - 1] + a[i];\/\/求前缀和 
	for (int i = 1; i <= n - 2; ++ i) {
		ll sum = a[i + 1];
		int x = log2 (a[i]) + 1;
		for (int j = i + 2; j <= n; ++ j) {
			if ((a[i] ^ a[j]) == pre[j - 1] - pre[i]) {\/\/看看这个区间是否满足这个性质 
				if (!op) s.insert ({i , j});\/\/正着直接插入 
				else s.insert ({n - j + 1 , n - i + 1});\/\/倒着 
			}
			if ((sum + a[i]) > (1ll << x)) break;\/\/超过了范围,break 
			sum += a[j];
		}
	}
}
int main() {
	scanf ("%d" , &n);
	for (int i = 1; i <= n; ++ i)
		scanf ("%d" , &a[i]);
	solve (0);\/\/正着来
	reverse (a + 1, a + 1 + n);
	solve (1);\/\/倒着来
	printf ("%d\n" , (int)s.size());\/\/输出答案
	return 0;
}

码字不易,求赞!

CF1526C1题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-11-28 11:57:37

题目链接

题意

从一个长度为 $n$ 的序列中选出任意个,使这个序列的和非负,求这个序列的长度

解题思路

输入,如果这个数就是非负整数,那么就加上这个数,否则就判断加上这个数是否为负数,如果不是负数,就将序列之和加上这个数,否则如果之前选的数比现在选这个数损失大,那么就把损失大的改成这个数。

AC Code

废话少说,上代码:

#include <bits\/stdc++.h>\/\/万能头
using namespace std;
#define ll long long
inline int read() {\/\/快读模板 
    register char k = getchar();
    register int x = 0 , flg = 1;
    while (k < '0' || k > '9') {
        if (k == '-')flg = -1;
        k = getchar();
    }
    while (k >= '0' && k <= '9') {
        x = x * 10 + k - '0';
        k = getchar();
    }
    return x * flg;
}
inline void print(ll num) {\/\/快输模板 
    if (num < 0)putchar('-') , num = -num;
    if (num > 9)print(num \/ 10);
    putchar(num % 10 + '0');
} 
int n;\/\/有n个数 
int main() {
    n = read();
    priority_queue <int> q;\/\/默认大根堆 
    ll sum = 0 , ans = 0;\/\/sum是当前序列的和,ans是序列的长度 
    for (int i = 1 , a; i <= n; ++ i) {\/\/循环 
        a = read();\/\/输入 
        if (a >= 0) ++ans , sum += a;\/\/如果当前这个数本来就是非负的,那么就加上这个数,序列长度加一 
        else {
            if (abs(a) <= sum) {\/\/如果当前的和加上a还是非负的, 那么就加上这个数,序列长度加一 
                sum -= abs(a);\/\/减去个数 
                q.push(abs(a));\/\/加入大根堆 
                ++ ans;\/\/序列长度加一 
            }else {
                if (!q.empty() && q.top() > abs(a)) {\/\/如果大根堆非空且之前选的数比这个负数小,就改成这个数 
                    ll f = q.top();q.pop(); 
                    sum += f - abs(a);\/\/换,因为是覆盖掉之前选的数,所以序列长度不加 
                    q.push(abs(a));\/\/把当前更好的方案入堆 
                }
            }
        }
    }
    print(ans);\/\/输出序列长度 
    return 0;
}

如果有哪里有错请大佬指出。

CF1288C题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-11-29 12:04:10

题解思路:

$2m$ 个数字

{

1 -- 1

2 -- 2

排序

a : 1 1 , b : 2 2 

a = [1 , 1] , b = [2 , 2]

}

对于一种合法的数组而言,每个数组出现的次数构成一个大小n的数组 = $[1 , 2]$

1 2 2 2

1 -- 1

2 -- 3

一个 $ab$ 对唯一对应一个数字出现的次数。

可以把题目转化成 :求 $1-n$ 之间的出现次数。

假设 $n = 4$ ,$m = 5$ :

1 -- ? 举例填 3

2 -- ? 举例填 1

3 -- ? 举例填 2

4 -- ? 举例填 4

1 1 1 2 3 3 ………… $a = []$ , $b = []$

数组 1 1 1 2 3 3 4 4 4 4 。

$a = [1 1 1 2 3]$ $b = [3 4 4 4 4]$

$3 + 1 + 2 + 4$ 必须 $= 2m$。

即对应的数加起来必须是 $2m$。

即求$a + b + c + d = 2m$。

$(a , b , c , d)$ 有多少种。

就变成了组合数学中的划分,模板。

结果:$c[n + 2m - 1][n - 1]$。

当然别忘了取模

(调了半个小时,突然发现 $ans = 0$)

代码:

#include <bits\/stdc++.h>\/\/万能头文件 
using namespace std;
#define ll int
#define mod 1000000007
int n , m;
inline ll read() {\/\/快读模板 
	register ll num = 0 , neg = 1;
	register char ch = getchar();
	while (ch < '0' && ch > '9'){
		if (ch == '-')neg = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		num = (num << 1) + (num << 3) + (ch ^ 48);
		ch = getchar();
	}
	return num * neg;
}
inline void print(int num) {\/\/快输模板 
	if (num < 0)putchar('-') , num = -num;
	if (num > 9)print(num \/ 10);
	putchar(num % 10 + '0');
}
long long kuaipow(long long a , long long b , long long m) {\/\/快速幂加取模 
	long long ans = 1;
	while (b > 0) {
		if (b & 1)ans = ans * a % m;
		a = a * a % m;
		b >>= 1; 
	}
	return ans; 
}
long long inv(long long a) {
	return kuaipow(a , mod - 2 , mod);\/\/调用函数
} 
int main() {
	n = read() , m = read();\/\/读入
	long long ans = 1;\/\/储存最终答案
	for (int i = 1; i <= n - 1; ++ i)\/\/计算组合数学 
		ans = ans * inv(i) % mod;\/\/要取模!!! 
	for (int i = 1; i <= 2 * m; ++ i)\/\/计算组合数学 
		ans = ans * inv(i) % mod;
	for (int i = 1; i <= n + 2 * m - 1; ++ i)\/\/计算组合数学 
		ans = ans * i % mod;
	print(ans) , putchar('\n');\/\/输出答案 
	return 0;
}

如果有错误请奆佬指出

CF1549B题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-11-29 17:38:34

题意:

  • 上面是黑棋。
  • 下边是白棋。
  • 如果对面是空的即可直接走过来 。
  • 如果对边相邻的棋子有并且没被吃掉,就可以吃掉它。
  • 求最多我方能到达最上方的人数。

贪心:

  • 如果自己有棋子,对边没有棋子,ans++。
  • 如果自己有棋子,对面旁边有棋子,并之前没有被标记过,ans++并标记。

代码:

#include <bits\/stdc++.h>\/\/万能头文件 
using namespace std;
#define ll int\/\/…… 
inline ll read() {\/\/快读模板 
	register ll num = 0 , neg = 1;
	register char ch = getchar();
	while (ch < '0' && ch > '9'){
		if (ch == '-')neg = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		num = (num << 1) + (num << 3) + (ch ^ 48);
		ch = getchar();
	}
	return num * neg;
}
inline void print(int num) {\/\/快输模板 
	if (num < 0)putchar('-') , num = -num;
	if (num > 9)print(num \/ 10);
	putchar(num % 10 + '0');
}
const int maxn = 2e5 + 10; 
int falg[maxn]; 
int main() {
	int t = read();
	while (t--) {
		int n = 0;
		cin >> n;\/\/输入n,n*n的棋盘 
		string s , w;
		cin >> s >> w;
		int ans = 0;\/\/储存答案 
		memset (falg , 0 , sizeof(falg));\/\/每一次用都要,不然会很惨,初始化 
		for (int i = 0; i < n; ++ i) {
			if (w[i] == '1') {\/\/如果我方这里有人 
				if (s[i] == '0')ans++;\/\/如果对方这里没人,就跳到这里,ans++
				else {\/\/否则就看看对方旁边有没有人并没有被吃掉,那么ans++ ,并标记这个人被吃掉了。 
					if (i - 1 >= 0 && falg[i - 1] == 0 && s[i - 1] == '1') {\/\/如果左上有敌人并没有被吃掉并且 i-1 合法。 
						ans++;\/\/ans++ 
						falg[i - 1] = 1;\/\/达标记 
					} else if (i + 1 < n && falg[i + 1] == 0 && s[i + 1] == '1') {\/\/如果右上有敌人并没有被吃掉并且 i+1 合法。 
						ans ++;\/\/ans++ 
						falg[i + 1] = 1;\/\/达标记 
					}
				}
			} 
		} 
		print(ans) , putchar('\n');\/\/输出结果 
	} 
	return 0;
}

如有错误请大佬指出

CF888D题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-12-04 14:59:54

题目大意:

给出 $n$ 和 $k$ 计算满足至少有 $(n-k)$ 个位置的值 $a[i]==i$ 的 $1-n$ 的全排列的个数

题解思路

通过计算,得出递推式:

$p_1 = 0 , p_2 = 1 , p_i = (n - 1) * (p_{i - 1} + p_{i - 2}) (i > 2)$

这样写代码就简单了!

代码:

#include <bits\/stdc++.h>\/\/万能头
using namespace std;
#define ll long long
const int a[5] = {0 , 0 , 1 , 2 , 9};
inline ll c(ll x , ll y) {
	ll re = 1;
	for (ll i = 1; i <= y; ++ i)re *= n - i + 1;
	for (ll i = 1; i <= y; ++ i)re \/= i;
	return re;
}
int main() {
	cin >> n >> k;\/\/输入
	int ans = 1;\/\/别忘了是1
	for (int i = 2; i <= k; ++ i)
		ans += c(n , i) * a[i];
	cout << ans << endl;\/\/输出答案
	return 0;
}

CF1486E题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-12-05 15:03:18

我们来看一个图: 从最上边的点,到最后一个点,边权是: $(w_1 + w_2)^2$

那么从一个点到另一个点,需要两步:

  • 第一步,移到中间点。
  • 第二步,移到目标点。

我们把点分为两类:

    1. 到达点:到达点就是到达一个点的最短路是多少
    1. 中转点:中转点就是要到达一个地方经过的点

如果我们把路径分为两种:

一: 中转点 $\impliedby$ 到达点,花费0边权

二: 到达点 $\impliedby$ 中转点,花费 $(w_1 + w_2) ^ 2$

但如果这样做的话,复杂度会很高,中转点的状态数是 $n^2$

但当前我们只关注 $w_1$ 是谁,而不关注中转点是谁。

所以我们可以把中转点改一下,中转点的状态数只有 $[50]$ 个就够了,因为 $w_i$ 最多只有 50。

$u_0$ 代表到达点 , $u_1$ 代表u是中转点,他是由1走过来的。

这就维护了他的最短路 $dis_{[u][0]} ,dis_{[u][1]} ……. dis_{[u][50]}$ 。

他的时间复杂度为 $\mathcal{O(n·mlongm)}$

看代码:

#include <bits\/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;\/\/根据题目范围
#define ll long long
#define itn int \/\/个人习惯,防手残
vector< pair<int , int> > e[maxn];\/\/定义变量
int n , m;
ll dis[maxn][55];
inline void dij() {
	memset (dis , 0x3f , sizeof (dis));\/\/每次用的时候,要初始化
	dis[1][0] = 0;
	priority_queue< pair <ll , pair<int , int> > > pq;
	pq.push({0 , {1 , 0}});
	while (!pq.empty()) {
		auto now = pq.top(); pq.pop();
		int u = now.second.first , u2 = now.second.second;
		for (auto &x : e[u]) {
			int v = x.first , w = x.second;
			if (u2 > 0) {
				if (dis[v][0] > dis[u][u2] + (u2 + w) * (u2 + w)) {
					dis[v][0] = dis[u][u2] + (u2 + w) * (u2 + w);
					pq.push({-dis[v][0] , {v , 0}});
				}
			}else {
				if (dis[v][w] > dis[u][u2]) {
					dis[v][w] = dis[u][u2];
					pq.push({-dis[v][w] , {v , w}});
				}
			}
		}
	}
}
int main() {
	cin >> n >> m;\/\/读入
	for (int i = 0; i < m; ++ i) {\/\/读入边
		int u , v , w;
		cin >> u >> v >> w;
		e[u].push_back({v , w});
		e[v].push_back({u , w});
	}
	dij();\/\/调用函数
	for (itn i = 1; i <= n; ++ i)\/\/输出
		if (dis[i][0] > int(1e18))cout << "-1 ";
		else cout << dis[i][0] << ' ';
	return 0;
}

CF1608A题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2021-12-12 12:24:03

题目大意:

给你一个数 $n$ , 生成一个数组,每个数在 $1-10^9$ ,并且是升序的。

题解思路:

因为 $1$ 能整除所有的数,所以我们避开 $1$ 就可以了,因为是升序的,那么我们就从 $2$ 到 $n+1$ 输出之间的所有数即可。

这样代码就好写了!!!

上代码:

#include <bits\/stdc++.h>
using namespace std;
#define ll long long
#define putt(x) cout << #x << ':' << x
#define inline il
#define register rg
#define max(a,b) a > b ? a : b
#define min(a,b) a < b ? a : b
#define inf 0x3f3f3f3f
#define itn int
#define it int
#define QWQ puts("QWQ")
const int mod = 1e9 + 7;
const int N = 110;
int test , n;\/\/test组数,输入的n
int main() {
	scanf ("%d" , &test);\/\/输入组数
	while (test--) {\/\/循环test次
		scanf ("%d" , &n);\/\/输入n
		for (int i = 2; i <= n + 1; ++ i)\/\/输出这个序列
			printf("%d " , i);
		printf ("\n");\/\/别忘了换行
  	}
    	\/\/QWQ
   	return 0;\/\/完结散花
}

P4767 [IOI2000]邮局题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-02-05 19:11:48

题意:

题很简单,大意是给你 $n$ 个村庄的坐标(就一个数字),让你盖 $p$ 个邮局,问你怎么盖能让所有村庄到邮局的距离之和最小。

思路:

首先显然动态规划嘛O(∩_∩)O

数组 $f_{i,j}=i$ 个村庄时盖 $j$ 个镖局的答案。

【再建一个数组 $min$ (写代码的时候当然不能用这个名字), $min_{i,j}=i$ 到 $j$ 这几个村庄如果只设一个镖局的答案,很显然应该放在中心,也就是 $(i+j)\div2$ 这里。】

---------状态转移方程分割线---------

  1. $f_{i,j}=\min{f_{k,j-1}+min_{k+1,j}}$

很显然不用过多解释了吧(假的。

首先, $j$ 个镖局最少要有 $j$ 个村庄 (这不是废话吗)

好,那么接下来,i个村庄j个镖局如果再加上 $x$ 个村庄这种方法是被允许的。

于是,$k$ 的范围应该在 $j-1$ 到 $i$ 之间。

  1. $min_{i,j}=min_{i,j-1}+location_{j}-location_{(i+j)\div2}$

$location_{i}$ 是第 $i$ 个村庄的坐标。

这也不多说了,刚才说过了(详见用【】框起来的部分)

-----------50分代码-----------

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int n , p , d[3005] , f[3005][305] , m[3005][3005];
int main(){
	scanf ("%d%d" , &n , &p);
	for(int i = 0; i < n; i ++)scanf ("%d" , &d[i]);
	for(int i = 0; i < n; i ++)
		for(int j = 0; j <= min(i , p - 1); j ++)
			f[i][j] = 0x3f3f3f3f;
	for(int i = 0; i < n; i ++){
		for(int j = i + 1; j < n; j ++)
			m[i][j] = m[i][j - 1] + d[j] - d[i + j >> 1];	
		f[i][0] = m[0][i];
	}
	for(int i = 1; i < n; i ++)
		for(int j = 1; j <= min(i , p - 1); j ++)
			for(int k = j - 1; k < i; k ++)
				f[i][j] = min(f[i][j] , f[k][j - 1] + m[k + 1][i]);
	printf ("%d" , f[n - 1][p - 1]);
	return 0;
}

但是这个程序的运算次数为 $30\times300\times300=27\times10^8=2.7\times10^9$,而现在一秒最多只能跑 $10^8$ 次,所以只能得 50 分。

但我们发现放邮局的地方是具有单调性的,因为如果放的再靠前的话他也不如放在后面好,所以我们可以用一个数组 $P_{i,j}$ 来记录,它代表前 $i$ 个村庄放第 $j$ 个邮局的地方,每次从 $P_{i,j-1}$ 开始枚举,因为他是有单调性的,所以可以从 $P_{i,j-1}$ 开始。

把这个加上的话,就可以去掉一些重复部分,所以可以过。

-----------AC 代码-----------

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int INF = 0x3f3f3f3f , N = 3010 , M = 310;
int n , p;
int d[N];
int f[N][M] , g[N][N] , P[N][N];
int i , j , k;
int main(){
	scanf ("%d%d" , &n , &p);
	for(i = 0; i < n; ++ i)scanf ("%d" , &d[i]);
	for(i = 0; i < n; ++ i)
		for(j = 0; j <= min(i , p - 1); j ++)
			f[i][j] = INF;
	for(i = 0; i < n; ++ i){
		for(j = i + 1; j < n; ++ j)
			g[i][j] = g[i][j - 1] + d[j] - d[(i + j) \/ 2];	
		f[i][0] = g[0][i];
	}
	for(i = 1; i < n; ++ i)
		for(j = 1; j <= min(i , p - 1); ++ j) {
			for(k = P[i][j - 1]; k < i; ++ k) 
				if (f[i][j] > f[k][j - 1] + g[k + 1][i]) {
					f[i][j] = f[k][j - 1] + g[k + 1][i];
					P[i][j] = k;
				}
		}
	printf ("%d" , f[n - 1][p - 1]);
	return 0;
}

Floyd

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2022-02-15 11:49:40

Floyd

Floyd有4个用途:

$\texttt{1.最短路}$

$\texttt{2.传递闭包}$

$\texttt{3.找最小环}$

$\texttt{4.恰好经过k条边的最短路(倍增)}$

初始化:

$$ d(i,j)=\left\{ \begin{aligned} i {=}\mathllap{/\,}j,d(i,j) = +\infty \ i==j,d(i,j)=0 \ \end{aligned} \right. $$

计算:

$for$ $k$ $\impliedby$ $1 - n$

$for$ $i$ $\impliedby$ $1 - m$

$for$ $j$ $\impliedby$ $1 - n$

$d_{k,i,j} = \min(d_{k-1,i,k} + d_{k-1,i,j} , d_{k-1,i,j})$;

Floyd是怎么DP的呢?

----------------DP 分析---------------- 动态规划

状态表示:$d_{k,i,j}$

集合:$\color{orange}\texttt{所有从i出发,最终走到j,且中间只经过节点编号不超过k的所有路径。}$ 属性:$\color{orange}\texttt{路径长度的最小值}(\min)$

状态计算:

$d_{k,i,j}$

$\texttt{所有不包含节点k的路径}$

$d_{k-1,i,j}$

$\texttt{不包含k就是只用1\~~~k-1这些点从i走到j}$。

$\texttt{所有包含节点k的路径}$。

$i->k->j$

$\texttt{从i出发经过k走到j。}$

$\texttt{我们可以把i\~~~k分成一段,把k\~~~j分成一段。}$

$\texttt{而从i走到k,不论怎么走,并不影响从k走到j。}$

$\texttt{因此我们要求最短路,而且是完全独立的两部分,那么就相当于求A+B,而由于它们是互相独立的,所以我们就取A的最小值B的最小值,相加就能得到从i->k->j的最短路。}$

----------------尝试去掉一维----------------

我们尝试去掉一维,看一看去掉之后,状态转移方程是否等价,如果等价,就可以去掉,否则就不可以去掉。

原方程:

$d_{k,i,j} = \min(d_{k-1,i,k} + d_{k-1,i,j} , d_{k-1,i,j})$

去掉一维后的方程:

$d_{i,j} = \min(d_{i,j} , d_{i,j} + d_{k,j})$

首先,$d_{k,i,j}$是一定等于$d_{k-1,i,j}$,所以,在取$\min$的时候,不包含$k$的就可以去掉一维。

而第包含$k$的也只用到了$k-1$,所以也可以去掉一维,这样就可以去掉一维了!

C++

共 105 篇博客