整理的算法模板合集: ACM模板
1. NC 打铁的箱子(规律 + 二进制)
- 首先分析数据范围。
n<1e9,所以哪怕 O ( n ) O(n) O(n)的算法都不可能通过,就更别想用01背包来完成了(看上去像是01背包的模型)。只能用一些结论 O ( 1 ) O(1) O(1)输出、二进制优化 O ( l o g n ) O(logn) O(logn)、折半查找等极为迅速的算法。
- 然后来分析题意。
我们发现给出的能用的数为: 2 , 3 , 5 , 9 2,3,5,9 2,3,5,9,题目干扰地说是前一项 ∗ 2 − 1 *2-1 ∗2−1,实际上我们可以很轻松地看出来这个数列应该是 2 0 + 1 、 2 1 + 1 、 2 2 + 1..... 2^0+1、2^1+1、2^2+1..... 20+1、21+1、22+1..... 。我们发现本题的关键是将题目所给的备选数据转换为二进制的数,而不是题目中给出的格式,然后就可以发现规律。我们发现这些数能够凑出来的数一定是 2 k 1 + k 2 . . . + k x + x 2^{k_1 + k_2...+k_x} + x 2k1+k2...+kx+x ,所以我们可以看看输入的数是否满足这个条件
- 最后计算答案。
我们可以枚举用了几个数,也就是 x x x ,然后按照上面的能够凑出来的公式来判断。那么我们可以先将 n n n 减去 x x x,然后检查 n − x n−x n−x 的二进制中是否恰好是 x x x 个 1 1 1 即可。
- 时间复杂度: O ( l o g n ) O(logn) O(logn)。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define debug(x) cout << x << "ok" << endl
typedef long long ll;
#define int long long
#define file freopen("1.in", "r", stdin);freopen("1.out", "w", stdout);
const int N = 500007, M = 5000007, INF = 0x3f3f3f3f;
using namespace std;
int lowbit(int x)
{
return x & (-x);
}
int t, n, m;
signed main()
{
scanf("%lld", &t);
while(t -- ){
bool flag = 0;
scanf("%lld", &n);
for(int i = 1; i <= 30; ++ i){
int x = n - i;
int num = 0;//二进制中x有几个1
while(x){
num ++ ;
x -= lowbit(x);
}
if(num == i){
flag = 1;
break;
}
}
if(flag){
puts("YES");
}
else puts("NO");
}
return 0;
}
2. NC 最优屏障(单调栈 + 前缀和,后缀和)
如图:
- 首先分析数据范围。
n < 5 e 4 n<5e4 n<5e4,所以只能用 O ( n l o g n ) O(nlogn) O(nlogn)或者 O ( n ) O(n) O(n)的算法来做。
- 然后再分析题意。
因为我们如果在中间放一个屏障的话,那么左右的山峰就被隔断了,也就是屏障左边只算左边的,屏障右边的只算右边的,按照这个思路我们要找的是一个最优的位置,使得只算左边和只算右边的比全部都算差的最大,所以大致的思路就是我们枚举每一个位置,看这个位置的差是否最大,这个过程是 O ( n ) O(n) O(n)。而这个差就是总的防御力 - (屏障左边的防御力+屏障右边的防御力)。我们思考如何求得左边,右边的防御力。
我们画图发现,首先,左右的山峰一定都可以互相看到,如果对于一个山峰而言,先看左边,所有高度小于他自己的山峰都可以算作自己的防御力,也就是从左往右单调递减,(如果是1高2低3更低,那么1只能和2算,2只能和3算)我们发现可以用一个单调栈维护,右边同理。那么现在的问题是如何在 O ( 1 ) O(1) O(1)或者 O ( l o g n ) O(logn) O(logn)的时间复杂度内求得左边的防御力和以及右边的防御力和,我们发现这不就是前缀和嘛,同理后面的我们可以维护一个后缀和,这样就可以 O ( 1 ) O(1) O(1)求得每个点防止屏障时该点左右的防御力和。
- 最后计算答案。
答案就是总的防御力 - (屏障左边的防御力+屏障右边的防御力) = m a x v = m a x { p r e [ n ] − ( p r e [ i − 1 ] + s u f [ i ] ) } maxv = max\{ pre[n] - (pre[i - 1] + suf[i])\} maxv=max{pre[n]−(pre[i−1]+suf[i])}
- 时间复杂度: O ( n ) O(n) O(n)。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<stack>
#define debug(x) cout << x << "ok" << endl
typedef long long ll;
#define file freopen("1.in", "r", stdin);freopen("1.out", "w", stdout);
const int N = 50007, M = 5000007, INF = 0x3f3f3f3f;
using namespace std;
int n, m, t, kcase;
int pre[N];//前缀和
int suf[N];//后缀和
int a[N];
stack<int>stk;
int main()
{
scanf("%d", &t);
while(t -- ){
scanf("%d", &n);
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[i]);
}
memset(pre, 0, sizeof pre);
memset(suf, 0, sizeof suf);
while(stk.size())stk.pop();
for(int i = 1; i <= n; ++ i){
int now = 0;
pre[i] = pre[i - 1];
while(stk.size() && stk.top() < a[i]){
stk.pop();
now ++ ;
}
if(stk.size())pre[i] += now + 1;
else pre[i] += now;
stk.push(a[i]);
}
while(stk.size())stk.pop();
for(int i = n; i >= 1; -- i){
int now = 0;
suf[i] = suf[i + 1];
while(stk.size() && stk.top() < a[i]){
stk.pop();
now ++ ;
}
if(stk.size())suf[i] += now + 1;
else suf[i] += now;
stk.push(a[i]);
}
int maxv = -INF, id = -1;
for(int i = 1; i <= n; ++ i){
if(maxv < (pre[n] - (pre[i - 1] + suf[i]))){
maxv = pre[n] - (pre[i - 1] + suf[i]);
id = i;
}
}
printf("Case #%d: %d %d", ++ kcase, id, maxv);
}
return 0;
}
3. CF993C Careful Maneuvering(bitset优化 + 枚举,模拟)
- 首先分析数据范围
n , m < 60 n,m<60 n,m<60,爆搜!不至于,至少我们可以很轻松地 O ( n 2 ) O(n^2) O(n2)枚举。
- 然后来分析题意
我们实际上可以把题意简化为:有两条平行于y轴的直线,他们是关于y轴对称的。每条直线上有一些点。每条直线上的点都与另外一条直线上的点连一条线,这条线与y轴有一个交点。选两个点,使经过这两个点的直线的两个端点数量最多。
某个点能摧毁的飞船一定是因为有两个飞船关于这个点对称。即若(-100,y1)和(100,y2)能互相摧毁,说明他们关于(0,(y1+y2)/2)对称。这里100是没有用的,我们可以离散化一下。
我们n*m枚举所有的左右点,找到他们的对称点,然后标记一下这个点可以将i,j这两个点删除,这里可以直接使用bitset标记,因为最后我们有两架飞机,需要合并,使用bitset可以很轻松地完成合并(使用 |
)最后取最大值即可。
- 时间复杂度 O ( n ∗ m + c n t 2 ∗ ( n + m ) 64 ) O(n * m + \frac{cnt^2 * (n + m)}{64}) O(n∗m+64cnt2∗(n+m))
需要注意的是我们使用get_mid
的时候,由于 a
和 b
都是 int
,所以直接相加除以 2
会下取整成 int
。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<bitset>
#include<map>
#define debug(x) cout << x << "ok" << endl
typedef long long ll;
#define file freopen("1.in", "r", stdin);freopen("1.out", "w", stdout);
const int N = 40007, M = 100007, INF = 0x3f3f3f3f;
using namespace std;
int n, m;
bitset<200>Glight[N], Gright[N];
map<double, int>id;
int l[M], r[M];
int cnt;
inline double get_mid(int a, int b)
{
return (double)(a + b) / 2;//啊这,这里要加(double)不然他会取整然后就WA at 3了
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++ i)
scanf("%d", &l[i]);
for(int i = 1; i <= m; ++ i)
scanf("%d", &r[i]);
for(int i = 1; i <= n; ++ i){
for(int j = 1; j <= m; ++ j){
double x = get_mid(l[i], r[j]);
if(id[x] == 0)//可能存在同一个对称点
id[x] = ++ cnt;
Glight[id[x]].set(i, 1);
Gright[id[x]].set(j, 1);
}
}
int ans = 0;
for(int i = 1; i <= cnt; ++ i){
for(int j = 1; j <= cnt; ++ j){
bitset<200>A, B;
A = Glight[i] | Glight[j];
B = Gright[i] | Gright[j];
int tmp = A.count() + B.count();
ans = max(ans, tmp);
}
}
printf("%d\n", ans);
return 0;
}