模块一:堆
模板
题目来源:【洛谷 3378】堆
首先是STL的堆(优先队列),性能稳定,功能强大,常数略大。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
priority_queue <int, vector<int>, greater<int> > q1;//小根堆
//priority_queue <int> q1; 默认是大根堆,这样定义。
int main(){
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i ++){
int a, b;
scanf("%d", &a);
if(a == 1) scanf("%d", &b);
if(a == 1) q1.push(b);
else if(a == 2) printf("%d\n", q1.top());
else if(a == 3) q1.pop();
}
return 0;
}
手写的堆,功能比较单一,函数需要自己写,根据题目特性写函数,效率高,常数小。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
struct heap{
int a[1000010], p;
heap(){memset(a, 0, sizeof a);p = 0;}
void up(int x){ //上浮
if(x>>1 == 0) return;
if(a[x] < a[x>>1]) swap(a[x], a[x>>1]), up(x>>1);
}
void down(int x){ //下沉
if((x<<1) > p) return;
if((x<<1)+1 <= p && a[x] > min(a[x<<1], a[(x<<1)+1])){
if(a[x<<1] < a[(x<<1)+1]){
swap(a[x<<1], a[x]), down(x<<1);
}else swap(a[(x<<1)+1], a[x]), down((x<<1)+1);
}else if(a[x] > a[x<<1]) swap(a[x], a[x<<1]);
}
inline void push(int val){a[++p] = val, up(p);}
inline void pop(){swap(a[1], a[p]), p --, down(1);}
inline int top(){return a[1];}
bool empty(){return p == 0;}
};
heap q1;
int main(){
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i ++){
int a, b;
scanf("%d", &a);
if(a == 1) scanf("%d", &b);
if(a == 1) q1.push(b);
else if(a == 2) printf("%d\n", q1.top());
else if(a == 3) q1.pop();
}
return 0;
}
优化Dijstra算法
题目来源:【洛谷 1951】收费站
看题目就是一道二分+最短路的题,这里用来练习一下Dijstra算法。
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <queue>
#define pii pair<ll,int>
#define mp(a,b) make_pair(a,b)
#define fi first
#define se second
using namespace std;
typedef long long ll;
const ll inf = 1e16;
const int maxn = 10010;
const int maxm = 100010;
int _first[maxn], _to[maxm], _next[maxm], _val[maxn], cnt, n, m, s, t, v, ans;
ll _dis[maxm], dis[maxn];
bool vis[maxn];
priority_queue <pii, vector<pii>, greater<pii> > q1;
void add(int a, int b, ll c){
_next[++cnt] = _first[a];
_first[a] = cnt;
_to[cnt] = b;
_dis[cnt] = c;
}
ll dij(int S, int T, int x){
if(_val[S] > x) return inf;
while(!q1.empty()) q1.pop();
for(int i = 1; i <= n; i ++) dis[i] = inf, vis[i] = 0;
dis[S] = 0;
q1.push(mp(dis[S], S));
while(!q1.empty()){
pii t = q1.top();
q1.pop();
if(vis[t.se]) continue;
vis[t.se] = 1;
if(vis[T]) return dis[T];
for(int i = _first[t.se]; i; i = _next[i]){
if(_val[_to[i]] <= x && !vis[_to[i]] && dis[_to[i]] > _dis[i] + dis[t.se]){
dis[_to[i]] = _dis[i] + dis[t.se];
q1.push(mp(dis[_to[i]], _to[i]));
}
}
}
return dis[T];
}
int main(){
scanf("%d%d%d%d%d", &n, &m, &s, &t, &v);
for(int i = 1; i <= n; i ++) scanf("%d", &_val[i]);
for(int i = 1; i <= m; i ++){
int a, b; ll c;
scanf("%d%d%lld", &a, &b, &c);
add(a,b,c), add(b,a,c);
}
int l = 0, r = 1e9, ans = -1;
while(l <= r){
int mid = (l+r) >> 1;
ll dd = dij(s, t, mid);
if(dd > v) l = mid + 1;
else r = mid - 1, ans = mid;
}
printf("%d", ans);
return 0;
}
堆的应用
题1 题目来源:【洛谷 2085】最小函数值
首先这些函数在定义域内都是单调递增的,所以我们可以把1这个位置的函数值压入堆中,每次取出一个最小的,再把定义域中下一个数的值压入堆中。
#include <iostream>
#include <queue>
#include <cstdio>
using namespace std;
#define maxn 10010;
class f1{
public:
int a1, b1, c1;
int now, val;
bool operator < (const f1 &b)const{
return val > b.val;
}
};
priority_queue <f1> q1;
int main(){
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
f1 t;
t = (f1){a,b,c,1,a+b+c};
q1.push(t);
}
while(m --){
f1 t = q1.top();
q1.pop();
printf("%d ", t.val);
t.now ++;
t.val = t.a1*t.now*t.now+t.b1*t.now+t.c1;
q1.push(t);
}
return 0;
}
题2 题目来源:【洛谷 3045】牛券
贪心的选择买牛的方案。如果现在还没用完
k
张券,钱就用没了,那肯定是要买金钱前
我的代码找不到了。。。
//by hzwer
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define pa pair<int,int>
#define inf 1000000000
#define eps 1e-8
#define ll long long
using namespace std;
ll read()
{
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
priority_queue<int,vector<int>,greater<int> >q;
int n,K,ans;
ll m,sum;
struct data{
int p,c;
}a[50005];
bool cmpc(data a,data b)
{
return a.c<b.c;
}
bool cmpp(data a,data b)
{
return a.p<b.p;
}
int main()
{
n=read();K=read();m=read();
for(int i=1;i<=n;i++)
a[i].p=read(),a[i].c=read();
sort(a+1,a+n+1,cmpc);
for(int i=1;i<=K;i++)
{
sum+=a[i].c;
q.push(a[i].p-a[i].c);
if(sum>m){printf("%d\n",i-1);return 0;}
if(i==n){printf("%d\n",n);return 0;}
}
sort(a+K+1,a+n+1,cmpp);
ans=K;
for(int i=K+1;i<=n;i++)
{
int t=inf;
if(!q.empty())t=q.top();
if(t<a[i].p-a[i].c)
{
sum+=t;q.pop();
q.push(a[i].p-a[i].c);
sum+=a[i].c;
}
else sum+=a[i].p;
if(sum>m)break;
else ans++;
}
printf("%d\n",ans);
return 0;
}
模块二:并查集
并查集可以实现最小生成树,还有重要的一部分用并查集做的题目,一些题目用加权并查集真的非常好做。
题1 题目来源:【洛谷 2814】家谱
用并查集维护家族之间的信息,用map离散化。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#include <string>
using namespace std;
map <string, int> map1;
int f_int, now;
string p[50010], t, f_string;
char c;
int fa[50010], cnt;
int find(int x){return fa[x] == x ? x : fa[x] = find(fa[x]);}
int main(){
for(int i = 1; i <= 50000; i ++) fa[i] = i;
c = getchar();
while(c != '$'){
cin >> t;
int now = map1[t];
if(now == 0) now = map1[t] = ++cnt, p[cnt] = t;
if(c == '#'){f_int = now;}
if(c == '+'){if(find(now) != find(f_int)) fa[fa[now]] = fa[f_int];}
if(c == '?'){cout << t << " " << p[find(now)] << endl;}
do c = getchar(); while(c!='#'&&c!='+'&&c!='?'&&c!='$');
}
return 0;
}
题2 题目来源:【洛谷 1955】程序自动分析
还是要用到并查集来维护信息,需要用map离散化。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
using namespace std;
int T, n, fa[200010];
struct node{
int a, b, c;
bool operator < (const node &t)const{return c > t.c;}
};
map <int,int> map1;
node p[100010];
int j, ok, cnt;
inline int find(int x){return fa[x] == x ? x : fa[x] = find(fa[x]);}
int main(){
scanf("%d", &T);
while(T --){
scanf("%d", &n);
map1.clear(), ok = 0, cnt = 0;
for(int i = 1; i <= n; i ++){
int a, b, c, t1, t2;
scanf("%d%d%d", &a, &b, &c);
t1 = map1[a];
if(t1 == 0) t1 = map1[a] = ++cnt;
t2 = map1[b];
if(t2 == 0) t2 = map1[b] = ++cnt;
p[i].a = t1;
p[i].b = t2;
p[i].c = c;
}
for(int i = 1; i <= cnt; i ++) fa[i] = i;
sort(p+1, p+1+n);
for(j = 1; p[j].c == 1; j ++){
if(find(p[j].a)!=find(p[j].b)) fa[fa[p[j].a]] = fa[p[j].b];
}
for( ; j <= n; j ++){
if(find(p[j].a) == find(p[j].b)){
printf("NO\n"), ok = 1;
break;
}
}
if(!ok) printf("YES\n");
}
map1.clear();
return 0;
}
题3 题目来源:【洛谷 1525】关押罪犯
我的思路是用加权并查集,如果每次两个人不能在一个监狱里权值设置为1,同一个监狱的权值为0,路径压缩的时候对权值进行
mod2
的操作。
把仇恨值按照降序排序,如果两个人不在同一个集合中,加入到一个集合里;如果在一个集合里,那这个仇恨值就是答案了。贪心性质很明显,不证了。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
struct node{
int a, b, val;
bool operator < (const node &t)const{
return val > t.val;
}
};
node p[100010];
int fa[20010], dis[20010], n, m;
inline int find(int x){
if(fa[x] == x) return x;
int t = fa[x];
fa[x] = find(fa[x]);
dis[x] = (dis[t] + dis[x]) % 2;
return fa[x];
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) fa[i] = i;
for(int i = 1; i <= m; i ++) scanf("%d%d%d", &p[i].a, &p[i].b, &p[i].val);
sort(p+1, p+1+m);
for(int i = 1; i <= m; i ++){
if(find(p[i].a) != find(p[i].b)){
dis[fa[p[i].a]] = (dis[p[i].a]^dis[p[i].b]) ? 0 : 1;
fa[fa[p[i].a]] = fa[p[i].b];
}else if(!(dis[p[i].a]^dis[p[i].b])){
printf("%d", p[i].val);
return 0;
}
}
printf("0");
return 0;
}
题4 题目来源:【POJ 1611】The Suspects
题意:
有很多组学生,在同一个组的学生经常会接触,也会有新的同学的加入。但是SARS是很容易传染的,只要在改组有一位同学感染SARS,那么该组的所有同学都被认为得了SARS。现在的任务是计算出有多少位学生感染SARS了。假定编号为0的同学是得了SARS的。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n, m, t, p;
int num[30010], fa[30010];
int find(int x){return fa[x]==x ? x : fa[x]=find(fa[x]);}
int main(){
while(scanf("%d%d", &n, &m) != EOF && n + m){
for(int i = 0; i <= n; i ++) fa[i] = i, num[i] = 1;
for(int i = 1; i <= m; i ++){
scanf("%d%d", &t, &p);
for(int j = 2, a; j <= t; j ++){
scanf("%d", &a);
if(find(a)!=find(p)){
num[fa[p]] += num[fa[a]];
fa[fa[a]] = fa[p];
}
}
}
printf("%d\n", num[fa[find(0)]]);
}
return 0;
}
更多练习题目:点击传送
模块三:双向链表
模板题 题目来源:【洛谷 1160】 队列安排
思路:
结构体+指针模拟双向链表,用内存池分配内存空间,设置两个端点指针,表示头和尾。
剩下的就是乱搞了,怎么说这也是初赛的内容。
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
struct node{
int num;
node *pre, *nxt;
};
node lis[100010];
node *pos[100010];
int a1[100010];
int n, m, cnt;
node *head, *p1, *p2, *t;
node* get_newone(int x){
node *newone = &lis[++cnt];
newone->num = x;
return newone;
}
void del(int x){
node *now = pos[x];
now->nxt->pre = now->pre;
now->pre->nxt = now->nxt;
return;
}
void print(){
for(node *i = p1->nxt; i != p2; i = i->nxt)
printf("%d ", i->num);
}
int main(){
scanf("%d", &n);
p1 = get_newone(0), p2 = get_newone(n+1);
t = get_newone(1);
p1->pre = NULL, p2->nxt = NULL;
p1->nxt = t, p2->pre = t;
t->nxt = p2, t->pre = p1;
pos[1] = t;
for(int i = 2; i <= n; i ++){
int a, b;
scanf("%d%d", &a, &b);
if(b == 0){ // left
node *now = pos[a];
node *newone = get_newone(i);
newone->nxt = now;
newone->pre = now->pre;
now->pre->nxt = newone;
now->pre = newone;
pos[i] = newone;
}else{ // right
node *now = pos[a];
node *newone = get_newone(i);
newone->nxt = now->nxt;
newone->pre = now;
now->nxt->pre = newone;
now->nxt = newone;
pos[i] = newone;
}
}
scanf("%d", &m);
for(int i = 1; i <= m; i ++) scanf("%d", &a1[i]);
sort(a1+1, a1+m+1), m = unique(a1+1, a1+m+1) - a1-1;
for(int i = 1; i <= m; i ++) del(a1[i]);
print();
return 0;
}
模块四:ST表
ST表主要应用于一些查询区间最值的题目,对于一段不变的区间,ST表可以做到 O(nlogn)−O(1) 预处理和每次查询,主要是利用倍增的思想,维护最值,详细算法请看倍增专题。
模板题 题目来源:【POJ 3264】Balanced Lineup
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
int t1[50010][20], t2[50010][20];
int main(){
int n, m, q;
scanf("%d%d", &n, &q);
m = log((n))/log(2.0);
for(int i = 1; i <= n; i ++) scanf("%d", &t1[i][0]), t2[i][0] = t1[i][0];
for(int j = 1; j <= m; j ++){
for(int i = 1; i <= n; i ++){
t1[i][j] = t1[i][j-1], t2[i][j] = t2[i][j-1];
if(i+(1<<(j))-1<= n){
t1[i][j] = max(t1[i][j-1], t1[i+(1<<(j-1))][j-1]);
t2[i][j] = min(t2[i][j-1], t2[i+(1<<(j-1))][j-1]);
}
}
}
for(int i = 1; i <= q; i ++){
int a, b;
scanf("%d%d", &a, &b);
int p =(log((b-a+1))/log(2.0));
printf("%d\n", max(t1[a][p], t1[b-(1<<p)+1][p]) - min(t2[a][p], t2[b-(1<<p)+1][p]));
}
return 0;
}