文章目录
- 数据结构
- 图论
- 数论
- 字符串
- DP动态规划
- STL
- algorithm常用函数
- 计算几何
- 其他
本篇文章主要是Mangata平时写代码所用到的代码模板库,如有不对请在评论区指出
先放一个我的 常数优化的博客: 传送门
再放一个我的 代码格式博客: 传送门
数据结构
并查集
并查集是一种集合数据结构,通过并查集我们可以快速查询两个元素是否是一个集合,下面是Mangata常用的板子
/*
作者:Mangata
路径压缩并查集
*/
#include<cstdio>
const int N = 10005;//节点数
int fa[N],n;
void init(int len) {//初始化,先让每个位置的父节点等于自身
for(int i=0; i<=n; ++i)
fa[i] = i;
}
int find(int x) {//查找x的祖先节点
int t = x;//路径压缩
while(t != fa[t])
t = fa[t];
while(x != fa[x]) {
int temp = fa[x];
fa[x] = t;
x = temp;
}
return x;
}
void merge(int a,int b) {//将x和y合并
a=find(a),b=find(b);
if(a != b) {
fa[b] = a;
n--;
}
}
int main()
{
int t,m,a,b;
scanf("%d",&t);
while(t--) {
scanf("%d%d",&n,&m);
init(n);
for(int i = 1; i <= m; ++i) {
scanf("%d%d",&a,&b);
merge(a,b);
}
printf("%d\n",n);//输出不同类别的总数目
}
return 0;
}
树状数组
二维单点修改,区间查询
例题:传送门
Code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 5000;
ll tree[N][N<<2];
int n,m;
int lowbit(int x) {
return -x & x;
}
inline void updata(int x,int y,int k) {
while(x <= n) {
int temp = y;
while(y <= m) {
tree[x][y] += k;
y += lowbit(y);
}
x += lowbit(x);
y = temp;
}
}
inline ll get(int x,int y) {
ll ans = 0;
while(x) {
int temp = y;
while(y) {
ans += tree[x][y];
y -= lowbit(y);
}
x -= lowbit(x);
y = temp;
}
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
int a,b,c,d,op;
while(~scanf("%d",&op)) {
if(op == 1) {
scanf("%d%d%d",&a,&b,&c);
updata(a,b,c);
}
else {
scanf("%d%d%d%d",&a,&b,&c,&d);
printf("%lld\n",get(c,d)-get(c,b-1)-get(a-1,d) + get(a-1,b-1));
}
}
return 0;
}
线段树
单点修改,区间查询
例题:HDU1754
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 2000005;
int n,m;
int a[N],tree[N << 2];
void push_up(int k) {
tree[k] = max(tree[k<<1],tree[k<<1|1]);
}
void build(int k, int l,int r) {
if(l == r) {
tree[k] = a[l];
}
else {
int mid = l + ((r-l)>>1);
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
push_up(k);
}
}
void updata(int p,int v,int l,int r,int k) {
if(l == r) {
a[p] += v, tree[k] += v;
}
else {
int mid = l + ((r-l)>>1);
if(p <= mid) {
updata(p,v,l,mid,k<<1);
}
else {
updata(p,v,mid+1,r,k<<1|1);
}
push_up(k);
}
}
int query(int L, int R,int l,int r,int k) {
if(L <= l && R >= r) {
return tree[k];
}
else {
int ans = -INF;
int mid = l+r >>1;
if(L <= mid) {
ans = max(ans,query(L,R,l,mid,k<<1));
}
if(R > mid) {
ans = max(ans,query(L,R,mid+1,r,k<<1|1));
}
return ans;
}
}
int main()
{
while(~scanf("%d%d",&n,&m)) {
for(int i = 1;i <= n; ++i) {
scanf("%d",&a[i]);
}
build(1,1,n);
char op;
int l,r;
while(m--) {
cin>>op;
if(op == 'Q') {
scanf("%d%d",&l,&r);
printf("%d\n",query(l,r,1,n,1));
}
else if(op == 'U'){
scanf("%d%d",&l,&r);
updata(l,r-a[l],1,n,1);
}
}
}
return 0;
}
区间更新、区间查询
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6+10;
ll tree[N<<2],tag[N<<2];
ll a[N];
inline ll ls(ll p) {return p<<1;}
inline ll rs(ll p) {return p<<1|1;}
void push_up_min(ll p) {//向上更新操作
tree[p] = min(tree[ls(p)],tree[rs(p)]);
}
void build(ll p,ll l,ll r) {//建树操作
if(l == r) {
tree[p] = a[l];
return;
}
ll mid = l + r >> 1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
push_up_min(p);
}
inline void push_down(ll p,ll l,ll r) {//向下更新操作
if(tag[p]) {
tag[ls(p)] += tag[p];
tag[rs(p)] += tag[p];
tree[ls(p)] += tag[p];
tree[rs(p)] += tag[p];
tag[p] = 0;
}
//更新两个儿子节点的tag和tree值
}
void updata(ll L ,ll R ,ll l ,ll r ,ll p ,ll k) {//[L,R]是我们想要修改的区间,
if(L <= l && R >= r) { //[l,r]是我们目前搜索到的区间
tree[p] += k;
tag[p] += k;
return;
}
push_down(p,l,r);//回溯前往下传递更新
ll mid = l + r >> 1;
if(L <= mid) updata(L,R,l,mid,ls(p),k);//如果我们想修改的区间比当前的中间节点小or等于那么就查询
if(R > mid) updata(L,R,mid+1,r,rs(p),k);//如果我们想修改的区间比当前中间节点大那么就查询
push_up_min(p);//回溯后往上传递更新
}
ll query(ll L, ll R,ll l,ll r,ll p) {//[L,R]是我们要查询的区间,[l,r]是当前节点存储的区间
ll res = 0x3f3f3f3f3f3f3f3f;
if(L <= l && R >= r) return tree[p];
ll mid = l + r >> 1;
push_down(p,l,r);
if(L <= mid)
res = min(res,query(L,R,l,mid,ls(p)));
if(R > mid)
res = min(res,query(L,R,mid+1,r,rs(p)));
return res;
}
int n,m;
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n; ++i) {
scanf("%lld",&a[i]);
}
build(1,1,n);
int q;
ll l,r,k;
int fg = 0;
for(int i = 1;i <= m; ++i) {
scanf("%lld%lld%lld",&k,&l,&r);
if(fg) continue;
if(query(l,r,1,n,1) >= k)
updata(l,r,1,n,1,-k);
else fg = i;
}
if(fg) {
printf("-1\n%d\n",fg);
}
else puts("0");
return 0;
}
主席树(区间第k小数模板)
#include<bits/stdc++.h>
using namespace std;
const int N = 200005;
const int M = 200005;
int n,m;
int a[N],idx;
vector<int> nums;//去重数组
struct Node{
int l,r;//左右子节点编号
int cnt;
}tr[N*4+N*18];//开N*4+N*lgN个空间
int root[N];//一共需要保存N个历史记录
int find(int x){
int l = 0,r = nums.size()-1;
while(l!=r){
int mid = (l+r)/2;
if(nums[mid] >= x) r = mid;
else l = mid+1;
}
return l;
}
int build(int l,int r){//建树,cnt都为0
int q = ++idx;
if(l == r) return q;
int mid = (l+r)/2;
tr[q].l = build(l,mid);
tr[q].r = build(mid+1,r);
return q;
}
int insert(int p,int l,int r,int x){
int q = ++idx;
tr[q] = tr[p];
if(l == r){
tr[q].cnt++;
return q;
}
int mid = (l+r)/2;
//应该更新哪一个值域区间
if(x<=mid)
tr[q].l = insert(tr[p].l,l,mid,x);
else
tr[q].r = insert(tr[p].r,mid+1,r,x);
tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
return q;
}
int query(int q,int p,int l,int r,int k){
if(l == r) return r;
//有多少个数落在值域为[l,mid]中
int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
int mid = (l+r)/2;
if(k<=cnt)
return query(tr[q].l,tr[p].l,l,mid,k);
else
return query(tr[q].r,tr[p].r,mid+1,r,k-cnt);
}
int main(){
int i,j;
cin>>n>>m;
for(i = 1;i<=n;i++){
cin>>a[i];
nums.push_back(a[i]);
}
sort(nums.begin(),nums.end());
nums.erase(unique(nums.begin(),nums.end()),nums.end());
root[0] = build(0,nums.size()-1);//下标从0开始
for(i = 1;i<=n;i++){
root[i] = insert(root[i-1],0,nums.size()-1,find(a[i]));
//建立可持久话线段树
}
for(i = 1;i<=m;i++){
int l,r,k;
cin>>l>>r>>k;
cout<<nums[query(root[r],root[l-1],0,nums.size()-1,k)]<<endl;
}
return 0;
}
单调栈
//单调栈:输出左边第一个比它小的数,如果没有输出-1
int s[N];
int top;
int main(){
int n,i,j,a;
cin>>n;
for(i = 1;i<=n;i++){
cin>>a;
while(top > 0 && s[top] >= a)
top--;
if(top == 0) cout<<"-1 ";
else cout<<s[top]<<" ";
s[++top] = a;
}
return 0;
}
单调队列
//滑动窗口维护区间的最小值
int q[N];
int l,r;
int main(){
int i,j;
int k;//滑动窗口大小
cin>>n>>k;
for(i =1;i<=n;i++) cin>>a[i];
for(i = 1;i<=n;i++){
if(l<r && i-q[l+1] >= k) l++;
while(l<r && a[q[r]]>a[i]) r--;
q[++r]=i;
if(i>=k)
cout<<a[q[l+1]]<<" ";
}
return 0;
}
Trie树
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 5e5+10;//请注意这里的数组大小要开字符串的个数*字符串的长度这么大
int n,m;
struct trie {
int nextt[N][26],cnt;//nextt[i][j]存储的是第i个字符下一个字符j的位置信息
int exit[N];//exit[i]表示以值为i结束的这个字符串是否存在
void insert(char *s,int l) {
int p = 0;//这个表示的是当前的位置指针
for(int i = 0;i < l; ++i) {
int c = s[i] - 'a';
if(!nextt[p][c]) nextt[p][c] = ++cnt;//如果下面一层是空的,那么就更新下一层的值
p = nextt[p][c];//更新当前的p指针
}
exit[p] = 1;
}
void find(char *s,int l) {
int p = 0;
for(int i = 0;i < l; ++i) {
int c = s[i] - 'a';
if(!nextt[p][c]) break;//这里表示没找到,那么我们直接退出即可
p = nextt[p][c];
}
if(exit[p] == 1) {
puts("OK");
exit[p] = 2;
}
else if(exit[p] == 2) {
puts("REPEAT");
}
else if(exit[p] == 0) {
puts("WRONG");
}
}
}T;
char S[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 0;i < n; ++i) {
scanf(" %s",S);
T.insert(S,strlen(S));
}
scanf("%d",&m);
for(int i = 0;i < m; ++i) {
scanf("%s",S);
T.find(S,strlen(S));
}
return 0;
}
01Trie树
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
#define ll long long
const int N = 1e5+10;
int n,m;
struct Trie {
ll nextt[N*32][2];
ll exit[N*32];
ll cnt = 0;
void init() {
memset(nextt,0,sizeof nextt);
memset(exit,0,sizeof exit);
cnt = 0;
}
void insert(ll k) {
ll p = 0;
for(int i = 32;i >= 0; --i) {
ll c = (k>>i) & 1;
if(!nextt[p][c]) nextt[p][c] = ++cnt;
p = nextt[p][c];
}
exit[p] = k;
}
ll find(ll k) {
ll p = 0;
for(int i = 32;i >= 0; --i) {
ll c = (k>>i) & 1;
if(nextt[p][c^1]) p = nextt[p][c^1];
else p = nextt[p][c];
}
return exit[p];
}
}T;
int main()
{
int t;
scanf("%d",&t);
for(int j = 1;j <= t;++j) {
scanf("%d%d",&n,&m);
ll k;
T.init();
for(int i = 0;i < n; ++i) {
scanf("%lld",&k);
T.insert(k);
}
printf("Case #%d:\n",j);
while(m--) {
scanf("%lld",&k);
printf("%lld\n",T.find(k));
}
}
return 0;
}
图论
最短路
迪杰斯特拉(堆优化+链式前向星)
#include<cstdio>
#include<cstring>
#include<queue>//
using namespace std;
const int N=2e5+5;//数据范围
struct edge{//存储边
int u,v,w,next;//u为起点,v为终点,w为权值,next为前继
};
edge e[N];
int head[N],dis[N],n,m,s,cnt;//head为链中最上面的,dis表示当前答案,n为点数,m为边数,s为起点,cnt记录当前边的数量
bool vis[N];//vis表示这个点有没有走过
struct node{
int w,to;//w表示累加的权值,to表示到的地方
bool operator <(const node &x)const{//重载“<”号
return w>x.w;
}
};
priority_queue<node>q;//优先队列(堆优化)
void add(int u,int v,int w){
++cnt;//增加边的数量
e[cnt].u=u;//存起点
e[cnt].v=v;//存终点
e[cnt].w=w;//存权值
e[cnt].next=head[u];//存前继
head[u]=cnt;//更新链最上面的序号
}//链式前向星(加边)
void Dijkstra(){
memset(dis,127,sizeof(dis));//初始化,为dis数组附一个极大值,方便后面的计算
dis[s]=0;//起点到自己距离为0
q.push(node{0,s});//压入队列
while(!q.empty()){//队列不为空
node x=q.top();//取出队列第一个元素
q.pop();//弹出
int u=x.to;//求出起点
if(vis[u]) continue;//已去过就不去了
vis[u]=true;//标记已去过
for(int i=head[u];i;i=e[i].next){
int v=e[i].v;//枚举终点
if(dis[v]>dis[u]+e[i].w){//若中转后更优,就转
dis[v]=dis[u]+e[i].w;//更新
q.push(node{dis[v],v});//压入队列
}
}
}
}
int main(){
int u,v,w = 1;
s = 1;
scanf("%d%d",&n,&m);//输入
for(int i=1;i<=m;++i){
scanf("%d%d",&u,&v);
add(u,v,w);
add(v,u,w);
}
Dijkstra();//DJ
printf("%d\n",dis[n]-1);//输出1-n的最短路
return 0;
}
最短路径计数
dijkstra拆点最短次短路计数
(只对最短路径计数时,不需要拆点,正常bfs或者dijkstra就可以了)
(注意,一定要先更新次短路点的信息,因为最短路没有更新的时候,次最短路可能更新;而次最短路更新的时候,最短路一定会更新;)
#define x first
#define y second
typedef pair<pair<int,int>,int> PII;
int n, m, S, F;
const int N=1010,M=20010;
int h[N], e[M], w[M], ne[M], idx;
int dis[N][2], cnt[N][2];//0表示最短路状态,1表示次短路状态
bool st[N][2];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int dijkstra(int root)
{
memset(dis,0x3f,sizeof dis);
memset(st,false,sizeof st);
memset(cnt,0,sizeof cnt);
dis[root][0]=0,cnt[root][0]=1;
priority_queue<PII,vector<PII>,greater<PII> > heap;
heap.push({{0,0},root});//依次存放最短距离,状态类型,点的id,注意,第一个一定要放最短距离
while(!heap.empty())
{
auto t=heap.top();
heap.pop();
int ver=t.y,type=t.x.y,distance=t.x.x,res=cnt[ver][type];
if(st[ver][type]) continue;
st[ver][type] = true;
for(int i=h[ver];i!=-1;i=ne[i])
{
int j=e[i];
if(dis[j][0]>distance+w[i])
{
dis[j][1]=dis[j][0],cnt[j][1]=cnt[j][0];
heap.push({{dis[j][1],1},j});
dis[j][0]=distance+w[i],cnt[j][0]=res;
heap.push({{dis[j][0],0},j});
}
else if(dis[j][0]==distance + w[i]) cnt[j][0]+=res;
else if(dis[j][1]>distance+w[i])
{
dis[j][1]=distance+w[i];
cnt[j][1]=res;
heap.push({{dis[j][1],1},j});
}
else if(dis[j][1]==distance + w[i])
{
cnt[j][1]+=res;
}
}
}
//dijkstra处理之后,要对终点的状态进行判断
int ans = cnt[F][0];
if (dis[F][0] + 1 == dis[F][1]) ans += cnt[F][1];
return ans;
}
最小生成树
kruskal
最小生成数基于并查集的写法
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 205
struct node{
int from,to;
long long cost;
}E[N*N];
int fa[N],n,m;
bool cmp(node a,node b){
return a.cost<b.cost;
}
int find(int x){
int k = x;
while(k != fa[k])
k = fa[k];
while(x != fa[x]) {
int t =fa[x];
fa[x] = k;
x = t;
}
return x;
}
void init(){
for(int i = 1; i <= n; ++i)
fa[i] = i;
}
int same(int x,int y){
return find(x) == find(y);
}
void merge(int a,int b){
a = find(a);
b = find(b);
if(a != b)
fa[b] = a;
}
long long kruskal(){
long long ans = 0;
sort(E+1,E+m+1,cmp);
for(int i = 1; i <= m; ++i){
if(same(E[i].from,E[i].to))
continue;
merge(E[i].from,E[i].to);
ans += E[i].cost;
}
return ans;//返回的是最小生成树的代价
}
int main(){
int u,v,w;
while(~scanf("%d%d",&m,&n)&&m)
{
init();
for(int i = 1; i <= m; ++i){
scanf("%d%d%d",&u,&v,&w);
E[i].from = u,E[i].to = v,E[i].cost = w;
}
long long k = kruskal();
int loc = find(1);
for(int i = 2; i <= n; ++i)
if(find(i) != loc){
k = -1;
break;
}
if(k == -1)
puts("?");
else
printf("%lld\n",k);
}
return 0;
}
prim
最小生成树基于prim的写法
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
#define P pair<int, int>
#define INF 0x3f3f3f3f
const int N = 1005;
int mp[N][N];
bool vis[N];
int dis[N];
int n,m;
//返回的是一个生成树每条边的和
int prim(int s) {
for(int i = 1; i <= n ; ++i) {
dis[i] = INF;
vis[i] = false;
}
dis[s] = 0;
int ans = 0;
while(true) {
int v = -1;
for(int u = 1; u <= n; ++u) {
if(!vis[u] && (v == -1 || dis[u] < dis[v])) {
v = u;
}
}
if(v == -1)
break;
vis[v] = true;
ans += dis[v];
//松弛操作 更新最短路径
for(int u = 1; u <= n; ++u) {
dis[u] = min(dis[u], mp[u][v]);
}
}
return ans;
}
int main()
{
int u,v,cost;
while(~scanf("%d%d",&m, &n) && m) {
memset(mp,INF,sizeof mp);
for(int i = 0; i < m; ++i) {
scanf("%d%d%d",&u, &v, &cost);
if(mp[u][v] > cost)
mp[u][v] = mp[v][u] = cost;
}
int ans = prim(1);
if(ans < INF) {
printf("%d\n",ans);
} else {
puts("?");
}
}
return 0;
}
次小生成树
非严格次小生成树
prime+dp O(V^2)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define p_queue priority_queue
ll gcd(ll a, ll b) {
return b == 0 ? a : gcd(b, a % b);
}
ll lcm(ll a, ll b) {
return a / gcd(a, b) * b;
}
int T, n;
double edge[1005][1005], dp[1005][1005], dis[1005];
bool vis[1005], used[1005][1005];
int pre[1005];
struct node
{
double x, y, value;
}point[1005];
double prime()
{
double mst = 0;
mem(vis, false);
mem(dp, 0);
mem(used, false);
vis[1] = true;
dis[1] = 0;
//以根节点初始化
for (int i = 2; i <= n; i++) {
dis[i] = edge[1][i];
pre[i] = 1;//记录可以到达点的父亲节点
}
for (int i = 2; i <= n; i++)
{
int index = -1;
double maxn = INF;
for(int j = 1 ; j <= n ; j ++)
{
if(!vis[j] && maxn > dis[j])
{
index = j;
maxn = dis[j];
}
}
mst += maxn;
vis[index] = true;
used[pre[index]][index] = used[index][pre[index]] = true; //记录已使用边
for(int j = 1 ; j <= n ; j ++)
{
//dp维护已走过点,两点间的最大边
//1 -> 2 -> 3 -> 4 , index = 4
//dis[index]代表3->4 dp[j][pre[index]]代表 1->3 , 2->3路径中边的最大值
if(vis[j] && j != index)
dp[index][j] = dp[j][index] = max(dp[j][pre[index]],dis[index]);
if(!vis[j] && dis[j] > edge[index][j])
{
dis[j] = edge[index][j];
pre[j] = index;
}
}
}
return mst;
}
int main(void)
{
scanf("%d",&T);
while(T --)
{
mem(edge,0);
scanf("%d",&n);
for(int i = 1 ; i <= n ; i ++)
scanf("%lf %lf %lf",&point[i].x,&point[i].y,&point[i].value);
for(int i = 1 ; i <= n ; i ++)
for(int j = 1 ; j <= n ; j ++)
if(i != j)
edge[i][j] = sqrt( (point[i].x-point[j].x)*(point[i].x-point[j].x) + (point[i].y-point[j].y)*(point[i].y-point[j].y));
double mst = prime();
double ans = -1;
for(int i = 1 ; i <= n ; i ++)
{
for(int j = 1; j <= n ; j ++)
{
if(i != j)
{
if(used[i][j])
ans = max(ans,(point[i].value+point[j].value)/(mst-edge[i][j]));
else
ans = max(ans,(point[i].value+point[j].value)/(mst-dp[i][j]));
}
}
}
printf("%.2f\n",ans);
}
}
方法二:倍增+kruskal O(MlogN)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define p_queue priority_queue
ll gcd(ll a, ll b) {
return b == 0 ? a : gcd(b, a % b);
}
ll lcm(ll a, ll b) {
return a / gcd(a, b) * b;
}
int T;
int n , m, cnt;
int head[1505],fa[1505],vis[1505],used[1505],deep[1505];
int maxn[1505][30] , bz[1505][30];
struct node
{
int f, t, next, w;
bool operator < (const node& no) const{
return no.w > w;
}
}edge[25000],b_edge[25000];
int findroot(int x)
{
if(fa[x] == x)
return x;
return fa[x] = findroot(fa[x]);
}
bool merge(int x, int y)
{
int fax = findroot(x);
int fay = findroot(y);
if(fax != fay) {
fa[fax] = fay;
return true;
}
return false;
}
void add(int f, int t , int w)
{
b_edge[cnt].t = t;
b_edge[cnt].w = w;
b_edge[cnt].next = head[f];
head[f] = cnt ++;
}
void Init()
{
cnt = 0;
mem(head,-1);
mem(used,0);
mem(vis,0);
for(int i = 1 ; i <= n ; i ++)
fa[i] = i;
}
int kruskal()
{
int tot = 0;
int mst = 0;
for(int i = 1 ; i <= m ; i ++)
{
if(tot == n-1)
break;
int u = edge[i].f, v = edge[i].t, w = edge[i].w;
if(merge(u,v))
{
tot ++;
mst += w;
used[i] = 1;
add(u,v,w);
add(v,u,w);
}
}
if(tot == n-1)
return mst;
return -1;
}
void bfs()
{
queue <int> q;
q.push(1);
vis[1] = 1;
deep[1] = 0;
while(!q.empty())
{
int no = q.front();
q.pop();
for(int i = head[no] ; i != -1 ; i = b_edge[i].next)
{
int v = b_edge[i].t, w = b_edge[i].w;
if(!vis[v])
{
vis[v] = 1;
deep[v] = deep[no] + 1;
bz[v][0] = no;
maxn[v][0] = w;
q.push(v);
}
}
}
}
void Deal()
{
for(int j = 1 ; j <= 20 ; j ++)
{
for(int i = 1 ; i <= n ; i ++)
{
bz[i][j] = bz[bz[i][j-1]][j-1];
maxn[i][j] = max(maxn[i][j-1],maxn[bz[i][j-1]][j-1]);
}
}
}
int LCA(int x, int y)
{
if(deep[x] < deep[y])
swap(x,y);
for(int i = 20 ; i >= 0 ; i --)
if(deep[bz[x][i]] >= deep[y])
x = bz[x][i];
if(x == y) return x;
for(int i = 20 ; i >= 0 ; i --)
{
if(bz[x][i] ^ bz[y][i])
x = bz[x][i] , y = bz[y][i];
}
return bz[x][0];
}
int path_maxn(int u , int v)
{
int ans = -INF;
for(int i = 20 ; i >= 0 ; i --)
{
if(deep[bz[u][i]] >= deep[v]) {
ans = max(ans, maxn[u][i]);
u = bz[u][i];
}
}
return ans;
}
int main(void)
{
cin>>T;
for(int t = 1 ; t <= T ; t ++)
{
cin>>n>>m;
Init();
for(int i = 1 ; i <= m ; i ++)
cin>>edge[i].f>>edge[i].t>>edge[i].w;
sort(edge+1,edge+1+m);
int mst = kruskal();
if(mst == -1)
{
printf("Case #%d : No way\n",t);
continue;
}
bfs();
Deal();
int se_mst = INF;
for(int i = 1 ; i <= m ; i ++)
{
if(!used[i])
{
int u = edge[i].f;
int v = edge[i].t;
int w = edge[i].w;
int lca = LCA(u,v);
int ans_ma = path_maxn(u,lca);
int ans_mi = path_maxn(v,lca);
se_mst = min(se_mst,mst-max(ans_ma,ans_mi)+w);
}
}
if(se_mst != INF)
printf("Case #%d : %d\n",t,se_mst);
else
printf("Case #%d : No second way\n",t);
}
}
严格次小生成树
倍增:O(mlogm)
#include <algorithm>
#include <iostream>
const int INF = 0x3fffffff;
const long long INF64 = 0x3fffffffffffffffLL;
struct Edge {
int u, v, val;
bool operator<(const Edge &other) const { return val < other.val; }
};
Edge e[300010];
bool used[300010];
int n, m;
long long sum;
class Tr {
private:
struct Edge {
int to, nxt, val;
} e[600010];
int cnt, head[100010];
int pnt[100010][22];
int dpth[100010];
// 到祖先的路径上边权最大的边
int maxx[100010][22];
// 到祖先的路径上边权次大的边,若不存在则为 -INF
int minn[100010][22];
public:
void addedge(int u, int v, int val) {
e[++cnt] = (Edge){v, head[u], val};
head[u] = cnt;
}
void insedge(int u, int v, int val) {
addedge(u, v, val);
addedge(v, u, val);
}
void dfs(int now, int fa) {
dpth[now] = dpth[fa] + 1;
pnt[now][0] = fa;
minn[now][0] = -INF;
for (int i = 1; (1 << i) <= dpth[now]; i++) {
pnt[now][i] = pnt[pnt[now][i - 1]][i - 1];
int kk[4] = {maxx[now][i - 1], maxx[pnt[now][i - 1]][i - 1],
minn[now][i - 1], minn[pnt[now][i - 1]][i - 1]};
// 从四个值中取得最大值
std::sort(kk, kk + 4);
maxx[now][i] = kk[3];
// 取得严格次大值
int ptr = 2;
while (ptr >= 0 && kk[ptr] == kk[3]) ptr--;
minn[now][i] = (ptr == -1 ? -INF : kk[ptr]);
}
for (int i = head[now]; i; i = e[i].nxt) {
if (e[i].to != fa) {
maxx[e[i].to][0] = e[i].val;
dfs(e[i].to, now);
}
}
}
int lca(int a, int b) {
if (dpth[a] < dpth[b]) std::swap(a, b);
for (int i = 21; i >= 0; i--)
if (dpth[pnt[a][i]] >= dpth[b]) a = pnt[a][i];
if (a == b) return a;
for (int i = 21; i >= 0; i--) {
if (pnt[a][i] != pnt[b][i]) {
a = pnt[a][i];
b = pnt[b][i];
}
}
return pnt[a][0];
}
int query(int a, int b, int val) {
int res = -INF;
for (int i = 21; i >= 0; i--) {
if (dpth[pnt[a][i]] >= dpth[b]) {
if (val != maxx[a][i])
res = std::max(res, maxx[a][i]);
else
res = std::max(res, minn[a][i]);
a = pnt[a][i];
}
}
return res;
}
} tr;
int fa[100010];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void Kruskal() {
int tot = 0;
std::sort(e + 1, e + m + 1);
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= m; i++) {
int a = find(e[i].u);
int b = find(e[i].v);
if (a != b) {
fa[a] = b;
tot++;
tr.insedge(e[i].u, e[i].v, e[i].val);
sum += e[i].val;
used[i] = 1;
}
if (tot == n - 1) break;
}
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v, val;
std::cin >> u >> v >> val;
e[i] = (Edge){u, v, val};
}
Kruskal();
long long ans = INF64;
tr.dfs(1, 0);
for (int i = 1; i <= m; i++) {
if (!used[i]) {
int _lca = tr.lca(e[i].u, e[i].v);
// 找到路径上不等于 e[i].val 的最大边权
long long tmpa = tr.query(e[i].u, _lca, e[i].val);
long long tmpb = tr.query(e[i].v, _lca, e[i].val);
// 这样的边可能不存在,只在这样的边存在时更新答案
if (std::max(tmpa, tmpb) > -INF)
ans = std::min(ans, sum - std::max(tmpa, tmpb) + e[i].val);
}
}
// 次小生成树不存在时输出 -1
std::cout << (ans == INF64 ? -1 : ans) << '\n';
return 0;
}
LCA
倍增模板:O(nlogn)
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<vector>
using namespace std;
int t,n,m;
const int N = 4e4+10;
int fa[N][31],cost[N][31],deep[N];//fa[i][j]表示第i个点向上跳2^j次方位置的祖先节点,cost[i][j]表示向上跳路径的花费,deep[i]表示的是第i个节点的深度
vector<int> V[N],W[N];
void init() {//初始化
for(int i = 0;i <= n; ++i) {
for(int j = 0;j <= 30; ++j)
fa[i][j] = cost[i][j] = 0;
deep[i] = 0;
V[i].clear(),W[i].clear();
}
}
void dfs(int root, int faa) {
fa[root][0] = faa;
deep[root] = deep[fa[root][0]] + 1;
for(int i = 1; i <= 30; ++i) {
fa[root][i] = fa[fa[root][i-1]][i-1];
cost[root][i] = cost[fa[root][i-1]][i-1] + cost[root][i-1];
}
int len = V[root].size();
for(int i = 0;i < len; ++i) {
if(V[root][i] == faa) continue;
cost[V[root][i]][0] = W[root][i];
dfs(V[root][i],root);
}
}
int LCA(int x,int y) {
if(deep[x] > deep[y]) swap(x,y);
int temp = deep[y] - deep[x];//深度差
int ans = 0;
for(int j = 0;temp;++j,temp>>=1) {//计算深度差的权值差
if(temp & 1) ans += cost[y][j],y = fa[y][j];
}
if(x == y) return ans;//如果节点一样直接返回值
for(int i = 30;i >= 0 && x != y; --i) {
if(fa[x][i] != fa[y][i]) {
ans += cost[x][i] + cost[y][i];
x = fa[x][i];
y = fa[y][i];
}
}
ans += cost[x][0] + cost[y][0];
return ans;
}
int main()
{
int t;
scanf("%d",&t);
while(t--) {
scanf("%d%d",&n,&m);
init();
for(int i = 1;i < n; ++i) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
V[u].push_back(v);
V[v].push_back(u);
W[u].push_back(w);
W[v].push_back(w);
}
dfs(1,0);
while(m--) {
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",LCA(u,v));
}
}
return 0;
}
tarjan模板:0(n+m)
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N = 10005;
const int M = 40005;
int n,m;
struct Node{
int v,ne,w;
}e[M];
int h[N],idx,d[N],vis[N],f[N];
//记录点的三种状态
//访问完毕并且已经回溯的标记2,访问完为回溯的标记为1
int st[N];
int dis[M],lca[M];//dis[i]表示第i次查询两点之间的最短距离
//lca[i]表示第i次查询两点的最近公共祖先
vector<PII> que[N];
void add(int a,int b,int c){
e[idx].v = b;
e[idx].ne = h[a];
e[idx].w =c ;
h[a] = idx++;
}
int find(int x){
if(f[x] != x) f[x] = find(f[x]);
return f[x];
}
void merge(int x,int y){
x = find(x);
y = find(y);
f[y] = x;
}
void tarjan(int x){
int i,j;
st[x] = 1;
for(i = h[x];i!=-1;i = e[i].ne){
int v = e[i].v;
if(st[v]!=0) continue;
d[v] = d[x] + e[i].w;
tarjan(v);
//v回溯后将v合并到x中
merge(x,v);
}
//处理每个与x相关的查询
for(i = 0;i<que[x].size();i++){
int y = que[x][i].first;
int id = que[x][i].second;
//如果y已经回溯结束
if(st[y] == 2){
//记录第i个查询的最近公共祖先
lca[id] = find(y);
//记录第i个查询两点之间的最近距离
dis[id] = d[x]+d[y]-2*d[lca[id]];
}
}
//开始回溯将状态标记为2
st[x] = 2;
}
int main(){
int i,j;
cin>>n>>m;//点数和询问
memset(h,-1,sizeof h);
for(i = 1;i<=n;i++) f[i] = i;
for(i = 1;i<n;i++){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
for(i = 1;i<=m;i++){
int a,b;
cin>>a>>b;
que[a].push_back({b,i});
que[b].push_back({a,i});
}
tarjan(1);
for(i = 1;i<=m;i++){
cout<<lca[i]<<" "<<dis[i]<<endl;
}
return 0;
}
二分图
匈牙利算法: O ( n × m ) O(n\times m) O(n×m)
int main(){
for(i = 1;i<=n1;i++){
memset(vis,0,sizeof vis);
//如果匹配成功,则匹配总数+1
if(find(i))ans++;
}
printf("%d",ans);
return 0;
}
//对于第x个,能够匹配返回true,不能匹配返回false
bool find(int x){
for(int i = h[x];i!=-1;i = e[i].ne){
int v = e[i].v;
//如果曾经查找过第v个点,但是没有成功
if(vis[v] == 0){
vis[v] = 1;
//第v个没有匹配或者第v个的匹配对象可以匹配成功
if(match[v] == 0 || find(match[v])){
match[v] = x; return true;
}
}
}
return false;
}
KM()算法,求二分图的带权最大匹配,O(n^3)
using namespace std;
typedef long long ll;
typedef pair<ll,ll> PII;
const int N = 605;
const int inf = 0x3f3f3f3f;
PII a[N];
ll n,b[N],c[N];
ll w[N][N], lx[N],ly[N];
ll linker[N], slack[N];
bool visy[N];
ll pre[N];
void bfs(ll k){
int i,j;
ll x,y = 0,yy = 0,delta;
memset(pre,0,sizeof(pre));
for(i =1;i<=n;i++) slack[i] = inf;
linker[y] = k;
while(1){
x = linker[y];delta = inf;visy[y] = true;
for(i=1;i<=n;i++){
if(!visy[i]){
if(slack[i] > lx[x] + ly[i] - w[x][i] ){
slack[i] = lx[x] + ly[i] - w[x][i];
pre[i] = y;
}
if(slack[i] < delta) delta = slack[i], yy = i;
}
}
for(i = 0;i <= n;i++){
if(visy[i]) lx[linker[i]] -= delta , ly[i] += delta;
else slack[i] -= delta;
}
y = yy ;
if(linker[y] == -1) break;
}
while(y) linker[y] = linker[pre[y]], y = pre[y];
}
ll KM(){
int i,j;
memset(lx,0,sizeof(lx));
memset(ly,0,sizeof(ly));
memset(linker,-1,sizeof(linker));
for(i = 1;i<=n;i++){
memset(visy,false, sizeof(visy));
bfs(i);
}
ll res = 0 ;
for(i = 1;i<=n;i++){
if(linker[i]!=-1){
res += w[linker[i]][i];
}
}
return res;
}
int main(){
int i,j;
while(cin>>n){
for(i = 1;i<=n;i++){
for(j = 1;j<=n;j++){
scanf("%d",&w[i][j]);
}
}
cout<<KM()<<endl;
}
return 0;
}
差分约束
解题步骤:
-
先将每个不等式xi<=xj+ck,转化成一条从点j到点i,长度为ck的一条边。
-
找一个超级源点,使得该源点一定可以遍历到所有边。
-
从源点求一遍单源最短路
结果一:如果存在负环,则原不等式组一定无解
结果二:如果没有负环,则d[i]就是原不等式组的一个可行解对于解得最大值和最小值如何求解:
- 当求解得最大值得时候,则应该将所有条件转化为:xi<=xj+ck,然后求最短路就是解得最大值。
- 当求解得最小值得时候,则应该将所有条件转化为:xi>=xj+ck,然后求最长路就是解得最小值。
求树的重心
int ans = inf;
//ans表示将重心删除后,剩余各个连通块中点数的最大值
//返回以u为跟的子树中点的数量
方法一:
int dfs(int u){
vis[u] = 1;
int size = 1,sum = 1;
for(int i = h[u];i!=-1;i = e[i].ne){
int v = e[i].v;
if(vis[v] == 0){
int a = dfs(e[i].v);
size = max(a,size);
sum +=a;
}
}
size = max(size,n-sum);
ans = min(ans,size);
return sum;
}
//方法二:
int dfs(int x,int last){
int i,j;
int size = 0,sum = 1;//以该点为跟的连通块的最大值
for(i = h[x];i!=-1;i = e[i].ne){
int v = e[i].v;
if(v == last) continue;
int t = dfs(v,x);
size = max(size,t);
sum+=t;
}
size = max(size,n-sum);
ans = min(ans,size);
return sum;
}
无向图割点和割边
Tarjan 算法求割点
vector<gg> graph[MAX];
// dfn表示每个结点的搜索次序
gg dfn[MAX], low[MAX];
bool visit[MAX], ans[MAX]; // ans标记该结点是否为割点
gg cnt = 0; // cnt表示搜索次序
void dfs(gg v, gg fa) {
visit[v] = true;
low[v] = dfn[v] = ++cnt;
gg child = 0;
for (gg i : graph[v]) {
if (not visit[i]) {
++child;
dfs(i, v);
low[v] = min(low[v], low[i]);
if (fa != v and low[i] >= dfn[v] and not ans[v]) {
ans[v] = true;
}
} else if (i != fa) {
low[v] = min(low[v], dfn[i]);
}
}
if (fa == v and child >= 2 and not ans[v]) {
ans[v] = true;
}
}
void tarjan() {
for (gg i = 1; i <= ni; ++i) {
if (not visit[i]) {
cnt = 0;
dfs(i, i);
}
}
}
Tarjan 算法求桥
vector<gg> graph[MAX];
// dfn表示每个结点的搜索次序
gg dfn[MAX], low[MAX], father[MAX];
bool ans[MAX]; // 如果ans[i]为true,则表示(father[x],x)为割边
gg cnt = 0; // cnt表示搜索次序
void dfs(gg v, gg fa) {
father[v] = fa;
low[v] = dfn[v] = ++cnt;
for (gg i : graph[v]) {
if (dfn[i] == 0) {
dfs(i, v);
low[v] = min(low[v], low[i]);
if (low[i] > dfn[v]) {
ans[i] = true;
}
} else if (dfn[i] < dfn[v] and i != fa) {
low[v] = min(low[v], dfn[i]);
}
}
}
void tarjan() {
for (gg i = 1; i <= ni; ++i) {
if (dfn[i] == 0) {
cnt = 0;
dfs(i, i);
}
}
}
Tarjan算法之割边
定义
(在无向图中):在一个连通图中,如果删去其中一条边后,连通块的数量会增多,那么我们称这条边为桥或者是割边.
算法
tarjan,只需要判定low[v]>dfn[u]即可(u为父,v为子)
解释:如果子节点在不走原路情况下到不了父节点或父节点之前的点,那么子节点只能走原路回到父节点及之前节点,原路为必经之路,断掉就会产生新的个联通块,符合割边定义。
例题:
HDU4738
code
#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<cstring>
#include<algorithm>
#define lson x<<1
#define rson x<<1|1
#define ll long long
#define rint register int
#define mid ((st[x].l + st[x].r) >> 1)
using namespace std;
template <typename xxx> inline void read(xxx &x) {
char c = getchar(),f = 1;x = 0;
for(;c ^ '-' && !isdigit(c);c = getchar());
if(c == '-') c = getchar(),f = -1;
for(;isdigit(c);c = getchar()) x = (x<<1) + (x<<3) + (c ^ '0');
x *= f;
}
template<typename xxx>void print(xxx x)
{
if(x<0){putchar('-');x=-x;}
if(x>9) print(x/10);
putchar(x%10+'0');
}
const int maxn = 2002;
const int inf = 0x7fffffff;
const int mod = 1e9 + 7;
struct edge {
int to,last,val;
// int fg;
}e[2000002];
int head[maxn],tot;
inline void add(int from,int to,int val) {
++tot;
e[tot].to = to;
// e[tot].fg = 0;
e[tot].val = val;
e[tot].last = head[from];
head[from] = tot;
}
int n,m;
int dfn[maxn],low[maxn],cnt;
int mark[2000002],mk;
inline void tarjan(int x,int in_edge) {//通过in_edge到达x
dfn[x] = low[x] = ++cnt;
for(rint i = head[x];i;i = e[i].last) {
if(!dfn[e[i].to]) {
tarjan(e[i].to,i);
if(low[x] > low[e[i].to]) low[x] = low[e[i].to];
if(low[e[i].to] > dfn[x]) {//子节点无法到达我及我之前,则联系我与子节点的边为桥
//这个判断可以防重边对桥的干扰,假设原图有一个桥,tarjan时绝对无法回到父节点及之前节点,
//但是有重边时 子节点通过重边“回到父节点”更新low[e[i].to] == dfn[x],结果无法被判为桥
// e[i].fg = e[i^1].fg = 1;//可这样记录重边
mark[++mk] = e[i].val;
}
}
else if(i ^ (in_edge ^ 1) && dfn[e[i].to] < low[x]) low[x] = dfn[e[i].to];//保证无重边时不会被父亲更新
}
}
int main()
{
while(1) {
tot = 1;
mk = 0;
cnt = 0;
read(n);read(m);
if(!n && !m) break;
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(head,0,sizeof(head));
for(rint i = 1;i <= m; ++i) {
int a,b,c;
read(a);read(b);read(c);
add(a,b,c);add(b,a,c);
}
int k = 0;
for(rint i = 1;i <= n; ++i) {
if(!dfn[i]) {
tarjan(i,0);
++k;
}
}
if(mk == 0) {//无桥,
printf("-1\n");
} else {
stable_sort(mark + 1,mark + mk + 1);
if(k > 1) printf("0\n");
else if(mark[1])printf("%d\n",mark[1]);
else printf("1\n");//没人守也要派一个人去占领
}
}
return 0;
}
/*
6 8
1 2 1
2 1 2
1 3 3
3 4 4
4 1 5
2 5 6
2 6 7
5 6 8
6 7
1 2 1
1 3 3
3 4 4
4 1 5
2 5 6
2 6 7
5 6 8
*/
树的直径
const int N = 10000 + 10;
int n, c, d[N];
vector<int> E[N];
void dfs(int u, int fa) {
for (int v : E[u]) {
if (v == fa) continue;
d[v] = d[u] + 1;
if (d[v] > d[c]) c = v;
dfs(v, u);
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d %d", &u, &v);
E[u].push_back(v), E[v].push_back(u);
}
dfs(1, 0);
d[c] = 0, dfs(c, 0);
printf("%d\n", d[c]);
return 0;
}
数论
数论常见公式
卡特兰数
错排公式
假设有n个元素,n个位置,每个元素都有自己唯一的正确位置,问,所有元素都处在错误位置有多少可能?
D(n)=(n-1)*( D(n-1) + D(n-2) ),易得 D(1)=0 , D(2)=1。
常用前n项和公式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iMNNyG8o-1636962675515)(D:\Typora\data\nh.png)]
GCD
GCD的四种方式,辗转相除法和更相减损法的循环写法和递归写法,但是一般来说要用gcd,直接调用__gcd函数就可
/*
作者:Mangata
GCD的四种写法
*/
#include<cstdio>
int gcd_1(int a,int b){//辗转相除法 循环写法
while(b > 0) {
int t = a%b;
a = b;
b = t;
}
return a;
}
int gcd_2(int a,int b) {//辗转相除法 递归写法
return b?gcd_2(b,a%b) : a;
}
int gcd_3(int a,int b) {//更相减损法 递归写法
if(a == 0)
return b;
if(b == 0)
return a;
if(a == b)
return a;
if(a > b)
return gcd_3(a-b,b);
if(a < b)
return gcd_3(b-a,a);
}
int gcd_4(int a,int b) {//更相减损法 循环写法
if(a == 0)
return b;
if(b == 0)
return a;
while(a != b) {
if(a > b)
a -= b;
else
{
int t = a;
a = b - a;
b = t;
}
}
return a;
}
int main()
{
int a,b;
while(~scanf("%d%d",&a,&b))
printf("gcd_1=%d\ngcd_2=%d\ngcd_3=%d\ngcd_4=%d\n",gcd_1(a,b),gcd_2(a,b),gcd_3(a,b),gcd_4(a,b));
}
拓展GCD
关于拓展GCD,我觉得比较核心的是贝祖定理,ax+by=gcd(a,b),方便我们求解不定方程,板子选取的题目是青蛙的约会
//青蛙的约会
#include<cstdio>
#include<algorithm>
#define ll long long
//返回的是GCD(a,b)
ll extgcd(ll a, ll b, ll &x0, ll &y0) {//注意这里是引用
if (!b) {
x0 = 1, y0 = 0;
return a;
}
ll d = extgcd(b, a%b, x0, y0);
ll t = x0;
x0 = y0;
y0 = t - a / b * y0;
return d;
}
int main()
{
ll x,y,m,l,x0,y0,n;
while(~scanf("%lld%lld%lld%lld%lld",&x, &y, &m, &n, &l)) {
x0 = 0, y0 = 0;
ll c = x - y;
ll a = n - m;
if(a < 0) {
a = -a;
c = -c;
}
ll d = extgcd(a, l, x0, y0);
if (c % d)
puts("Impossible");
else
printf("%lld\n",(c/d*x0 % (l/d) + l/d) % (l/d));
}
return 0;
}
素数判断
朴素素数判断
/*
作者:Mangata
朴素素数判断模板
*/
#include<cstdio>
#include<algorithm>
#include<cstring>
bool is_prime(int x) {
if(x == 0 || x == 1)
return false;
for(int i = 2; i*i <= x; ++i) {
if(x % i == 0)
return false;
}
return true;
}
int main()
{
while(~scanf("%d",&x)) {
if (is_prime(x))
puts("Yes");
else
puts("No");
}
return 0;
}
埃式筛
/*
作者:Mangata
埃式筛模板
*/
//时间复杂度 :O(n*log(log(n)))
#include<cstdio>
#include<algorithm>
#include<cstring>
const int N = 10000005;
bool vis[N] = {true,true};//初始化
int main()
{
int n,x;//离线处理
for(int i = 2; i*i <= N; ++i) {
if (!vis[i]) {
for(int j = i*2; j <= N; j += i) //把素数的倍数筛掉
vis[j] = true;
}
}
while(~scanf("%d",&x)) {
if (vis[x])
puts("No");
else
puts("Yes");
}
return 0;
}
欧拉筛
/*
作者:Mangata
欧拉筛模板
*/
//时间复杂度为 O(n)
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
const int N = 10000005;
int prime[N];
bool vis[N];
void get_prime() {
memset(vis,true,sizeof vis);
memset(prime,0,sizeof prime);
vis[0] = vis[1] = false;
for(int i = 2; i <= N; ++i) {
if (vis[i]) {
prime[++prime[0]] = i;
}
for(int j = 1;j <= prime[0] && i*prime[j] <= N; ++j) {
vis[i * prime[j]] = false;
if (i % prime[j] == 0) //避免重复筛选
break;
}
}
}
int main()
{
int n;
get_prime();
while(~scanf("%d",&n)) {
if (vis[n])
puts("Yes");
else
puts("No");
}
return 0;
}
快速幂&龟速乘
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
//快速幂
ll quick_pow(ll x, ll n, ll mod) {
ll res = 1;
while(n > 0) {
if(n & 1) res = (res * x)% mod;
x = (x * x) % mod;
n >>= 1;
}
return res;
}
//快速乘
ll quick_pow2(ll x, ll n, ll mod) {
ll res = 0;
x%= mod;
n%= mod;
while(n) {
if(n & 1) {
res = (res + x) % mod;
}
x = (x%mod + x%mod) % mod;
n >>= 1;
}
return res%mod;
}
int main()
{
ll a,b,c,n;
// while(~scanf("%lld%lld%lld",&a,&b,&c)) {
// printf("%lld\n",quick_pow(a,b,c));
// }
ll key,temp;
while(~scanf("%lld%lld",&n,&c)) {
scanf("%lld",&key);
key %= c;
for(ll i = 1; i < n; ++i) {
scanf("%lld",&temp);
key = quick_pow2(key,temp,c);
}
printf("%lld\n",key);
}
return 0;
}
组合数模板
ll qpow(ll a,ll b,ll c) {
ll ans = 1;
while(b) {
if(b & 1) ans = (ans * a) % c;
b >>= 1;
a = (a * a) % c;
}
return ans;
}
ll inv(ll x,ll c) {//逆元
return qpow(x,c-2,c);
}
ll C(ll a,ll b, ll c) {//组合数C(b,a) a>=b
ll ans = 1;
for(int i = 1;i <= b; ++i) {
ans = ans * (a-i+1) % c;
ans = ans * inv(i,c) % c;
}
return ans;
}
SG函数
题目大意:给定nn堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜
#include<bits/stdc++.h>
using namespace std;
const int N = 105;
int f[N];
int n,a[N];
int sg(int x){
int i,j;
if(f[x] != -1) return f[x];
unordered_set<int> se;
//每一堆可以变成不大于原来那堆的任意大小的两堆
for(i = 0;i<x;i++){
for(j = 0;j<x;j++){
//SG函数理论,多个独立局面的SG值,等于这些局面SG值的异或和。
se.insert(sg(i)^sg(j));
}
}
//找到最小的不能到达的数
for(i = 0;;i++){
if(se.count(i) == 0) return f[x] = i;
}
}
int main(){
int i,j;
cin>>n;
int ans = 0;
memset(f,-1,sizeof f);
for(i = 1;i<=n;i++){
cin>>a[i];
ans = ans ^ sg(a[i]);
}
//如果ans不为0则先手必胜
if(ans == 0) cout<<"No"<<endl;
else cout<<"Yes"<<endl;
return 0;
}
高斯消元
不带模数的求行列式
void det() {
const double EPS = 1E-9;
int n;
vector<vector<double> > a(n, vector<double>(n));
double det = 1;
for (int i = 0; i < n; ++i) {
int k = i;
for (int j = i + 1; j < n; ++j)
if (abs(a[j][i]) > abs(a[k][i])) k = j;
if (abs(a[k][i]) < EPS) {
det = 0;
break;
}
swap(a[i], a[k]);
if (i != k) det = -det;
det *= a[i][i];
for (int j = i + 1; j < n; ++j) a[i][j] /= a[i][i];
for (int j = 0; j < n; ++j)
if (j != i && abs(a[j][i]) > EPS)
for (int k = i + 1; k < n; ++k) a[j][k] -= a[i][k] * a[j][i];
}
cout << det;
}
带模数的求行列式
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mod 1000000007
ll n;
const int N = 1e2+10;
ll A[N][N];
ll AA;
ll quick_pow(ll a, ll n) {
ll ans = 1;
while(n) {
if(n & 1) ans = ans * a % mod;
a = a * a % mod;
n >>= 1;
}
return ans;
}
ll inv(ll a) {
return quick_pow(a, mod - 2);
}
void gauss(){
ll ans = 1;
for(int i = 1; i <= n; i++){
for(int j = i; j <= n; j++) {
if(A[j][i]){
for(int k = i; k <= n; k++) swap(A[i][k], A[j][k]);
if(i != j) ans = -ans;
break;
}
}
// if(!A[i][i]) return 0;
for(ll j = i + 1, iv = inv(A[i][i]); j <= n; j++) {
ll t = A[j][i] * iv % mod;
for(int k = i; k <= n; k++)
A[j][k] = (A[j][k] - t * A[i][k] % mod + mod) % mod;
}
ans = (ans * A[i][i] % mod + mod) % mod;
}
if(ans == AA) puts("+");
else puts("-");
// return ans;
}
char ch[100000];
int main()
{
int t;
scanf("%d",&t);
while(t--) {
AA = 0;
memset(A,0,sizeof A);
scanf("%d%s",&n,&ch);
bool fg = false;
for(int i = 0,len = strlen(ch);i < len; ++i) {
if(ch[i] == '-') {
fg = true;
continue;
}
AA = (AA * 10 + ch[i] - '0') % mod;
}
if(fg) AA = -AA;
for(int i = 1;i <= n; ++i) {
for(int j = 1;j <= n; ++j) {
scanf("%lld",&A[i][j]);
}
}
gauss();
}
return 0;
}
字符串
Manacher模板
Manacher算法主要应用于回文字符串上面,通过字符串翻倍我们能很轻松的通过对称的关系找到子字符串的回文长度。
/*
作者:Mangata
manacger模板
*/
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
#define N 210005
char S[N],str[N*2+5];
int len1,len2,ans,p[N*2+5];
void init() {//数组初始化,即数组长度翻倍
str[0] = '$';//为了防止数组越界
str[1] = '#';
for(int i = 0; i < len1; ++i){
str[i * 2 + 2] = S[i];
str[i * 2 + 3] = '#';
}
len2 = len1 * 2 + 2;
str[len2] = '*';
}
void manacher() {//manacher
init();
int id = 0,mx = 0;//mx表示的是当前计算回文串最右边字符的最大值
for(int i=0; i < len2; ++i){
p[i]=mx > i?min(p[id*2-i],mx-i) : 1;//p[i]=mx>i?min(p[id*2-i],m-i):1;
for(; str[i+p[i]] == str[i-p[i]]; p[i]++);//如果字符串对称则对称长度增加
if(p[i]+i > mx)//如果大于当前的最大长度则更新
mx = p[i]+i, id = i;
}
}
int main() {
while(~scanf("%s",S)){
len1 = strlen(S);
manacher();
int maxlen = 0;
for(int i = 0; i < len2; ++i)
maxlen = max(p[i], maxlen);
printf("%d\n",maxlen-1);
}
return 0;
}
KMP模板
KMP是一个字符串匹配算法,通过此算法能达到线性的匹配时间复杂度,通过对字串求next数组,让我们字符串不回溯(或者说是减少步骤)
/*
作者:Mangata
manacger模板
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10005
int nextt[N];
char S[N],T[N];
int len1,len2;
void get_next() {//获取next数组
int i = 0,j = -1;
nextt[0] = -1;//很重要
while(i < len1){
if(j == -1 || S[i] == S[j])
nextt[++i] = ++j;
else
j = nextt[j];
}
}
int kmp(){//返回的是子串在主串中存在的次数
int cnt = 0;//表示的是子串在主串中存在的次数
int i = 0,j = 0;
get_next();
while(i < len2)
{
if(j == -1 || T[i] == S[j])
i++,j++;
else
j = nextt[j];
if(j == len1)//表示的是子串在主串中存在的次数
j=0,cnt++;//子串指针归零
}
return cnt;
}
int main(){
while(~scanf("%s",T))
{
if(T[0]=='#')
break;
scanf("%s",S);
len1 = strlen(S);
len2 = strlen(T);
printf("%d\n",kmp());
}
return 0;
}
字符串hash(自然溢出)
ull base = 131,hash[100008],p[100008]; string s;
//求每个前缀的hash
void get_hashs(ull hashs[]){
p[0] = 1;
for(int i = 1;i<=n;i++){
hashs[i] = hashs[i-1]*base+s[i]-'a'+1;
p[i] = p[i-1]*base;
}
}
//得到每个子串的hash
ull get_hash(int l,int r,ull hashs[]){
return hashs[r]-hashs[l-1]*p[r-l+1];
}
AC自动机
求模式串在文本串里面匹配的次数
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#define maxn 1000005
#define maxm 500005
using namespace std;
int tree[maxm][26],fail[maxm],flag[maxn],cnt;
char str[maxn];
void init()
{
memset(fail,0,sizeof(fail));
memset(tree,0,sizeof(tree));
memset(flag,0,sizeof(flag));
cnt=0;
}
void insert(char *ch)
{
int len=strlen(ch);
int root=0;
for(int i=0;i<len;++i)
{
int id=ch[i]-'a';
if(!tree[root][id])
tree[root][id]=++cnt;
root=tree[root][id];
}
flag[root]++;
}
void getfail()
{
queue<int>q;
for(int i=0;i<26;++i)
{
if(tree[0][i])
{
fail[tree[0][i]]=0;
q.push(tree[0][i]);
}
}
while(!q.empty())
{
int now=q.front();
q.pop();
for(int i=0;i<26;++i)
{
if(tree[now][i])
{
fail[tree[now][i]]=tree[fail[now]][i];
q.push(tree[now][i]);
}
else
{
tree[now][i]=tree[fail[now]][i];
}
}
}
}
int find(char *ch)
{
int now=0,ans=0;
int len=strlen(ch);
for(int i=0;i<len;++i)
{
int id=ch[i]-'a';
now=tree[now][id];
for(int j=now;j&&flag[j]!=-1;j=fail[j])
{
ans+=flag[j];
flag[j]=-1;
}
}
return ans;
}
int main(void)
{
int t,n;
char ss[60];
scanf("%d",&t);
while(t--)
{
init();
scanf("%d",&n);
for(int i=0;i<n;++i)
{
scanf("%s",ss);
insert(ss);
}
getfail();
scanf("%s",str);
printf("%d\n",find(str));
}
}
DP动态规划
背包模板
目前只写三种常用的背包模板(01、完全、多重),主要也是其他的背包都比较抽象,不好直接写代码。
01背包
/*
作者:Mangata
01背包问题模板 滚动数组优化
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10005
int f[N];
int v[N],w[N];
int n,V;
int main()
{
scanf("%d%d",&n,&V);
for(int i = 1; i <= n; ++i) scanf("%d%d",&w[i],&v[i]);
for(int i = 1; i <= n; ++i)
{
for(int j = V; j >= w[i]; --j)
{
f[j] = max(f[j],f[j-w[i]]+v[i]);
}
}
printf("%d\n",f[V]);
return 0;
}
完全背包
/*
作者:Mangata
完全背包问题模板 滚动数组优化
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10005
int f[N];
int v[N],w[N];
int n,V;
int main()
{
scanf("%d%d",&n,&V);
for(int i = 1; i <= n; ++i) scanf("%d%d",&w[i], &v[i]);
for(int i = 1; i <= n; ++i)
{
for(int j = w[i]; j <= V; ++j)
{
f[j] = max(f[j], f[j-w[i]]+v[i]);
}
}
printf("%d\n",f[V]);
return 0;
}
多重背包
/*
作者:Mangata
多重背包问题模板 二进制枚举优化
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10005
int f[N];
int v[N],w[N],p[N];
int n,V;
int main()
{
scanf("%d%d",&n,&V);
for(int i = 1; i <= n; ++i) scanf("%d%d%d",&w[i],&v[i],&p[i]);
for(int i = 1; i <= n; ++i)
{
int num = min(p[i],V/w[i]);
for(int k = 1; num > 0; k <<= 1)
{
if(k > num)
k = num;
num -= k;
for(int j = V; j >= w[i]*k; --j)
f[j] = max(f[j], f[j-w[i]*k]+v[i]*k);
}
}
printf("%d\n", f[V]);
return 0;
}
单调队列优化的背包:
const int N = 1005;
const int M = 20005;
int v[N],w[N],s[N];//体积,价值,数量
int dp[M],g[M], q[M],n,m;
int main(){
int i,j,k;
cin>>n>>m;
for(i = 1;i<=n;i++){
cin>>v[i]>>w[i]>>s[i];
}
for(i = 1;i<=n;i++){
//枚举v[i]的每个余数
memcpy(g,dp,sizeof g);//滚动数组优化
//g表示dp[i-1][],dp表示dp[i][];
for(j = 0;j<v[i];j++){
int l = 0,r = 0;
for(k = j;k<=m;k+=v[i]){
//维护队列中k到k-s[i]*v[i]的最大值的体积
//单调队列中最大值存储物品i的数量大于s[i],出队
if(l<r && (k-q[l+1])/v[i] > s[i]) l++;
//除去比当前小的元素
while(l<r&&g[k]>=g[q[l+1]]+(k-q[l+1])/v[i]*w[i]) r--;
q[++r]=k;
dp[k] = g[q[l+1]]+(k-q[l+1])/v[i]*w[i];
}
}
}
cout<<dp[m]<<endl;
return 0;
}
超大背包问题
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 45;
typedef long long ll;
ll w[N],v[N],W;
int n;
struct Node {
ll w,v;
}ps[1 << (N/2)];//表示的是第一个堆枚举的可能的情况总数
bool cmp(Node a, Node b) {//排序函数,因为待会我们搜索的时候是按照W的大小进行二分
if(a.w != b.w)
return a.w < b.w;
return a.v > b.v;
}
int erfen(int l,int r,ll k) {//二分搜索
while(l + 1 < r) {
int mid = l + r >> 1;
if(ps[mid].w <= k) {
l = mid;
}
else {
r = mid;
}
}
//此时的r表示的是大于k的第一个位置
return r - 1;
}
void slove() {
int len1 = n / 2;
for(int i = 0, len = 1 << len1; i < len; ++i) {//二进制枚举第一个堆可能的情况
int tempw = 0, tempv = 0;
for(int j = 0;j < len1; ++j) {
if(i >> j & 1) {//是否选取
tempw += w[j];
tempv += v[j];
}
}
ps[i] = (Node){tempw,tempv};
}
sort(ps,ps+(1<<len1),cmp);//排序
int m = 1;
for(int i = 1, len = 1 << len1; i < len; ++i) {//这里筛出无用的选取组合,
if(ps[m - 1].v < ps[i].v) {//这样能让ps从0到m-1的元素都是w和v升序的
ps[m++] = ps[i]; //也就是让后一个元素的v一定是大于前一个元素的v
}
}
ll ans = 0;
for(int i = 0,len = (1 << (n - len1)); i < len; ++i) {//二进制枚举第二个堆的选取情况
int tempw = 0, tempv = 0;
for(int j = 0; j < n - len1; ++j) {
if(i >> j & 1) {
tempw += w[len1 + j];
tempv += v[len1 + j];
}
}
if(tempw <= W) {
int loc = erfen(-1,m,W-tempw);
ll key = 0;
if(loc != -1)//可能第一个堆什么也不选就满了
key = ps[loc].v;
ans = max(ans,key + tempv);//选取最优情况
}
}
printf("%lld\n",ans);
}
int main()
{
scanf("%d",&n);
for(int i = 0; i < n; ++i) {
scanf("%lld",&w[i]);
}
for(int i = 0; i < n; ++i) {
scanf("%lld",&v[i]);
}
scanf("%lld",&W);
slove();
return 0;
}
数位DP
例一
给定两个正整数 aa 和 bb,求在 [a,b][a,b] 中的所有整数中,每个数码(digit)各出现了多少次。
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int N = 15;
ll f[N][2][N][2];
int num[N]; //num来存这个数每个位子上的数码
/*
记忆化搜索。
len是当前为从高到低第几位。issmall表示当前位是否和num[len]相等,0是相等,1是不相等。
sum表示当前数字出现的次数。zero表示之前是否是前导0。d是当前在算的数码。
*/
ll dfs(int len, bool issmall, int sum, bool zero, int d)
{
ll ret = 0;
if (len == 0) return sum; //边界条件
if (f[len][issmall][sum][zero] != -1) return f[len][issmall][sum][zero]; //记忆化
for (int i = 0; i < 10; i ++){
if (!issmall && i > num[len]) break;
/*
由于我们是从高位到低位枚举的,所以如果之前一位的数码和最大数的数码相同,这一位就只能枚举到num[len];
否则如果之前一位比最大数的数码小,那这一位就可以从0~9枚举了。
*/
ret += dfs(len-1, issmall || (i<num[len]), sum+((!zero || i) && (i==d)), zero && (i == 0), d);
/*
继续搜索,数位减一,issmall的更新要看之前有没有相等,且这一位有没有相等;
sum的更新要看之前是否为前导0或者这一位不是0;
zero的更新就看之前是否为前导0且这一位继续为0;
d继续传进去。
*/
}
f[len][issmall][sum][zero] = ret;
//记忆化,把搜到的都记下来
return ret;
}
ll solve(ll x, int d)
{
int len = 0;
while (x){
num[++ len] = x%10;
x /= 10;
} //数字转数位
memset(f, -1, sizeof f); //初始化
return dfs(len, 0, 0, 1, d); //开始在第len位上,最高位只能枚举到num[len]所以issmall是0,sum=0,有前导0。
}
int main()
{
ll a, b; //注意都要开long long
scanf("%lld%lld", &a, &b);
for (int i = 0; i < 10; i ++)
printf("%lld%c", solve(b, i)-solve(a-1, i), i == 9 ? '\n' : ' ');
return 0;
}
例二
1 到 N 中有多少个数满足其二进制表示中恰好有 K 个 1。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int a[70];
ll dp[70][70], N;
int K;
ll dfs(int p, int limit, int k) {
if (p == 0) return k == K;
if (!limit && ~dp[p][k]) return dp[p][k];
int up = limit ? a[p] : 1;
ll ans = 0;
for (int i = 0; i <= up; ++i) {
ans += dfs(p - 1, limit && up == i, k + (i == 1));
}
if (!limit) dp[p][k] = ans;
return ans;
}
ll calc(ll v) {
memset(dp, -1, sizeof(dp));
int p = 0;
do {
a[++p] = v & 1;
v /= 2;
} while (v);
return dfs(p, 1, 0);
}
int main() {
cin >> N >> K;
cout << calc(N) << endl;
return 0;
}
STL
vector
v.push_back(item):向v后加一个元素O(1)
v.pop_back():删除v最后一个元素O(1)
v.size():获取v中元素个数O(1)
v.resize(n):把v的长度重新设定为n个元素O(|n-size|)
v.empty():判断v是否为空O(1)
v.clear():清空v中的元素O(size)
v[index]:获取v中下标为index的元素O(1)
v.begin();返回指向第一个元素的迭代器
v.end();返回指向vector末尾(最后一个元素之后的那个位置)的迭代器
v.front();返回第一个元素
v.back();返回最后一个元素
v.erase(iterator loc);删除loc所指元素并返回下一元素迭代器
v.erase(iterator start, iterator end);删除[start, end)之间的元素,并返回最后一个被删除元素的下个元素的迭代器
去重操作:vt.erase(unique(vt.begin(), vt.end()), vt.end());
v.insert(v.begin()+4,3); 在第五个元素前插入一个元素
priority_queue优先队列(默认为大根堆)
pq.push(item):添加元素 O(logn)
pq.pop():使优先级最高的出队O(logn)
pq.top():获取优先级最高的元素O(1)
pq.size():获取元素个数O(1)
pq.empty():是否为空O(1)
优先队列的定义:
priority_queue<int> q1; //默认从大到小,大顶堆
priority_queue<int ,vector<int>,less<int> > q2; //降序队列,大顶堆
priority_queue<int ,vector<int>,greater<int> > q3; //升序队列,小顶堆
对于结构体定义:
struct T1{//法一 强烈推荐
int x,y;
friend bool operator < (T1 a,T1 b){
return a.x<b.x;
}
};
priority_queue<T1> q1;
struct T1{//法二
int x,y;
bool operator < (const T1 &a) const{
return x<a.x; //大顶堆
}
};
priority_queue<T1> q1;
struct T2{int x,y;};//法三
bool operator <(T2 a,T2 b){
return a.x>b.x;
}
priority_queue<T2> q2;
struct T3{int x,y;};//法四
struct tmp{ //重写仿函数
bool operator() (T3 a,T3 b){
return a.x<b.x; //大顶堆
}
};
priority_queue<T3, vector<T3>,tmp> q3
set(二叉搜索树)
s.insert(item): 插入元素O(logn)
s.size():获取元素的个数O(1)
s.empty():判断是否为空O(1)
s.clear():清空s O(n)
s.find(item):在s中查找item并返回其iterator(迭代器),找不到的话返回s.end()O(logn)
s.count(item):返回s中item的数量,因为集合中的元素不能重复,因此只能返回0或1 O(logn)
s.erase(it):删除s中it指向位置的元素
s.erase(item):删除s中值为item的元素
s.earse(it1,it2);删除[it1,it2) 这个区间(迭代器)对应位置的元素
iterator lower_bound( const key_type &key ); 返回一个迭代器,指向键值 >= key的第一个元素。
iterator upper_bound( const key_type &key ); 返回一个迭代器,指向键值 > key的第一个元素。
Set的定义:
struct T1{
int key,value;
bool operator < (const T1 &a) const {
return key<a.key;//按照升序排列
}
};
struct T2{
int key,value;
};
struct T2cmp{
bool operator () (const int &a,const int &b){
if(abs(a-b)<=k)
return false;
return a<b;
}
};
int main(){
int i,j;
set<T1> s1;
set<T2,T2cmp> s2;
set<string> ss1;//等于set<string,less<int> > ss1;从小到大
set<string,greater<string> > ss2;//从大到小
set<string,greater<string> > ::iterator itsey;
//set的遍历
set<string> :: iterator it;
for(it = ss1.begin();it!=ss1.end();it++){
cout<<*it<<endl;
}
return 0;
}
Multiset
c.erase(elem) 删除与elem相等的所有元素,返回被移除的元素个数。
c.erase(pos) 移除迭代器pos所指位置元素,无返回值。
map
mp.size():获取元素个数O(1)
mp.empty():判断是否为空O(1)
mp.clear():清空mp O(1)
mp.find(key):在map中查找key并返回其iterator,找不到的话返回mp.end() O(logn)
mp.count(key):在map中找key的数量,由于每个key都是唯一的,只会返回0或1
mp[key] 可以直接访问到键值队key---value中的value,如果不存在这样的键值对,那么mp[key]返回的是value类型默认构造器所构造的值,并将该键值对插入到map中
mp[key]=tmp:可以把键值对key---value中的value赋值为tmp,如果没有对应的键值对,则将该键值对插入到map中复杂度: O(logn)
mp.insert(make_pair(key,value)):在mp中插入键值对key----value。一般不这样用,想要插入键值对的话直接使用mp[key]=value即可,map已经对[]运算符重载过了.
哈希表:
#include <unordered_map>
unordered_map
pair
#include <utility>头文件中
pair<T1, T2> p1; //创建一个空的pair对象(使用默认构造),它的两个元素分别是T1和T2类型,采用值初始化。
pair<T1, T2> p1(v1, v2); //创建一个pair对象,它的两个元素分别是T1和T2类型,其中first成员初始化为v1,second成员初始化为v2。
make_pair(v1, v2); // 以v1和v2的值创建一个新的pair对象,其元素类型分别是v1和v2的类型。
p1 < p2; // 两个pair对象间的小于运算,其定义遵循字典次序:如 p1.first < p2.first 或者 !(p2.first < p1.first) && (p1.second < p2.second) 则返回true。
p1 == p2// 如果两个对象的first和second依次相等,则这两个对象相等;该运算使用元素的==操作符。
p1.first; // 返回对象p1中名为first的公有数据成员。
p1.second; // 返回对象p1中名为second的公有数据成员。
algorithm常用函数
unique()去重函数
功能:”删除”序列中所有相邻的重复元素(只保留一个)。此处的删除,并不是真的删除,而是指重复元素的位置被不重复的元素给占领了(它并没有将重复的元素删除,而是把重复的元素放到数组的最后面藏起来了)由于它”删除”的是相邻的重复元素,所以在使用unique函数之前,一般都会将目标序列进行排序。对于数组而言返回去重后最后一个元素的指针,而其他容器则是返回去重后最后一个元素的迭代器。
iterator unique(iterator it_1,iterator it_2);
其中这两个参数表示对容器中[it_1,it_2)范围的元素进行去重,返回值是一个迭代器,它指向的是去重后容器中不重复序列的最后一个元素的下一个元素。
使用:
sort(vt.begin(), vt.end());
vt.erase(unique(vt.begin(), vt.end()), vt.end());
sort()函数
降序排列sort(s+1,s+1+n,greater());
求第k小数
nth_element(a,a+k,a+n),函数只是把下标为k的元素放在了正确位置,对其它元素并没有排序,当然k左边元素都小于等于它,右边元素都大于等于它,所以可以利用这个函数快速定位某个元素
二分函数:
lower_bound
(起始地址,结束地址,要查找的数值) 返回的是>=x的第一个数的地址。upper_bound
(起始地址,结束地址,要查找的数值) 返回的是>x的第一个数的地址binary_search
(起始地址,结束地址,要查找的数值) 返回的是是否存在这么一个数,是一个bool值。
在从小到大的排序数组中:
-
lower_bound( begin,end,num)
:从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。 -
upper_bound( begin,end,num)
:从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
在从大到小的排序数组中,重载lower_bound()
和upper_bound()
-
lower_bound( begin,end,num,greater())
:从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。 -
upper_bound( begin,end,num,greater())
:从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标
注意一点这个lower_bound
和upper_bound
只能用于非降序列,如果要用于非升序列的那要加一个greater <int> ()
计算几何
计算几何基本模板
#include <bits/stdc++.h>
using namespace std;
const double pi = acos(-1.0); //高精度圆周率
const double eps = 1e-8; //偏差值
const int maxp = 1010; //点的数量
int sgn(double x){ //判断x是否等于0
if(fabs(x) < eps) return 0;
else return x<0?-1:1;
}
int Dcmp(double x, double y){ //比较两个浮点数:0 相等;-1 小于;1 大于
if(fabs(x - y) < eps) return 0;
else return x<y ?-1:1;
}
//---------------平面几何:点和线--------
struct Point{ //定义点和基本运算
double x,y;
Point(){}
Point(double x,double y):x(x),y(y){}
Point operator + (Point B){return Point(x+B.x,y+B.y);}
Point operator - (Point B){return Point(x-B.x,y-B.y);}
Point operator * (double k){return Point(x*k,y*k);} //长度增大k倍
Point operator / (double k){return Point(x/k,y/k);} //长度缩小k倍
bool operator == (Point B){return sgn(x-B.x)==0 && sgn(y-B.y)==0;}
bool operator < (Point B){return sgn(x-B.x)<0 || (sgn(x-B.x)==0 && sgn(y-B.y)<0);} //用于凸包
};
typedef Point Vector; //定义向量
//点积
double Dot(Vector A,Vector B){return A.x*B.x + A.y*B.y;}
//向量的长度
double Len(Vector A){return sqrt(Dot(A,A));}
//向量长度的平方
double Len2(Vector A){return Dot(A,A);}
//求向量A和B的夹角
double Angle(Vector A,Vector B){return acos(Dot(A,B)/Len(A)/Len(B));}
//叉积,大于0则B在A的逆时针方向;小于0则B在A的顺时针方向;等于0则B与A共线,可能是同方向的,也可能是反方向的
double Cross(Vector A,Vector B){return A.x*B.y - A.y*B.x;}
//三角形ABC面积的2倍
double Area2(Point A, Point B, Point C){return Cross(B-A, C-A);}
//两点的距离,方法一
double Distance(Point A, Point B){return hypot(A.x-B.x,A.y-B.y);}
//两点的距离,方法二
double Dist(Point A,Point B){return sqrt((A.x-B.x)*(A.x-B.x) + (A.y-B.y)*(A.y-B.y));}
//向量A的单位法向量
Vector Normal(Vector A){return Vector(-A.y/ Len(A), A.x/ Len(A));}
bool Parallel(Vector A, Vector B){return sgn(Cross(A,B)) == 0;}//向量平行或重合
//向量A逆时针旋转rad度
Vector Rotate(Vector A, double rad){
return Vector(A.x*cos(rad)-A.y*sin(rad), A.x*sin(rad)+A.y*cos(rad));
}
struct Line{
Point p1,p2;//线上的两个点
Line(){}
Line(Point p1,Point p2):p1(p1),p2(p2){}
// Line(Point x,Point y){p1 = x;p2 = y;}
// Point(double x,double y):x(x),y(y){}
//根据一个点和倾斜角 angle 确定直线,0<=angle<pi
Line(Point p,double angle){
p1 = p;
if(sgn(angle - pi/2) == 0){p2 = (p1 + Point(0,1));}
else{p2 = (p1 + Point(1,tan(angle)));}
}
//ax+by+c=0
Line(double a,double b,double c){
if(sgn(a) == 0){
p1 = Point(0,-c/b);
p2 = Point(1,-c/b);
}
else if(sgn(b) == 0){
p1 = Point(-c/a,0);
p2 = Point(-c/a,1);
}
else{
p1 = Point(0,-c/b);
p2 = Point(1,(-c-a)/b);
}
}
};
typedef Line Segment; //定义线段,两端点是Point p1,p2
//返回直线倾斜角 0<=angle<pi
double Line_angle(Line v){
double k = atan2(v.p2.y-v.p1.y, v.p2.x-v.p1.x);
if(sgn(k) < 0)k += pi;
if(sgn(k-pi) == 0)k -= pi;
return k;
}
//点和直线关系:1 在左侧;2 在右侧;0 在直线上
int Point_line_relation(Point p,Line v){
int c = sgn(Cross(p-v.p1,v.p2-v.p1));
if(c < 0)return 1; //1:p在v的左边
if(c > 0)return 2; //2:p在v的右边
return 0; //0:p在v上
}
// 点和线段的关系:0 点p不在线段v上;1 点p在线段v上。
bool Point_on_seg(Point p, Line v){
return sgn(Cross(p-v.p1, v.p2-v.p1)) == 0 && sgn(Dot(p - v.p1,p- v.p2)) <= 0;
}
//两直线关系:0 平行,1 重合,2 相交
int Line_relation(Line v1, Line v2){
if(sgn(Cross(v1.p2-v1.p1,v2.p2-v2.p1)) == 0){
if(Point_line_relation(v1.p1,v2)==0) return 1;//1 重合
else return 0;//0 平行
}
return 2; //2 相交
}
//点到直线的距离
double Dis_point_line(Point p, Line v){
return fabs(Cross(p-v.p1,v.p2-v.p1))/Distance(v.p1,v.p2);
}
//求在直线上的投影点
Point Point_line_proj(Point p, Line v){
double k=Dot(v.p2-v.p1,p-v.p1)/Len2(v.p2-v.p1);
return v.p1+(v.p2-v.p1)*k;
}
//点p对直线v的对称点
Point Point_line_symmetry(Point p, Line v){
Point q = Point_line_proj(p,v);//先求点p在直线v的投影点
return Point(2*q.x-p.x,2*q.y-p.y);
}
//点到线段的距离
double Dis_point_seg(Point p, Segment v){
if(sgn(Dot(p- v.p1,v.p2-v.p1))<0 || sgn(Dot(p- v.p2,v.p1-v.p2))<0) //点的投影不在线段上
return min(Distance(p,v.p1),Distance(p,v.p2));//到两端点的距离取最小值
return Dis_point_line(p,v); //点的投影在线段上
}
//求两直线ab和cd的交点
//**调用前要保证两直线不平行或重合,先判断两直线是否平行
Point Cross_point(Point a,Point b,Point c,Point d){ //Line1:ab, Line2:cd
double s1 = Cross(b-a,c-a);
double s2 = Cross(b-a,d-a); //叉积有正负
return Point(c.x*s2-d.x*s1,c.y*s2-d.y*s1)/(s2-s1);
}
//两线段是否相交:1 相交,0不相交
//如果求两线段交点,先判断两线段是否相交,相交则转化成两直线求交点
bool Cross_segment(Point a,Point b,Point c,Point d){//Line1:ab, Line2:cd
double c1=Cross(b-a,c-a),c2=Cross(b-a,d-a);
double d1=Cross(d-c,a-c),d2=Cross(d-c,b-c);
return sgn(c1)*sgn(c2)<0 && sgn(d1)*sgn(d2)<0;//注意交点是端点的情况不算在内
}
//---------------平面几何:多边形----------------
struct Polygon{
int n; //多边形的顶点数
Point p[maxp]; //多边形的点
Line v[maxp]; //多边形的边
};
//判断点和任意多边形的关系: 3 点上; 2 边上; 1 内部; 0 外部
//点pt,多边形的点Point *p,多边形点的数量n,多边形顶点按照顺时针或者逆时针方向排列
int Point_in_polygon(Point pt,Point *p,int n){
for(int i = 0;i < n;i++){ //点在多边形的顶点上,下标从0开始
if(p[i] == pt)return 3;
}
for(int i = 0;i < n;i++){//点在多边形的边上
Line v=Line(p[i],p[(i+1)%n]);
if(Point_on_seg(pt,v)) return 2;
}
int num = 0;
for(int i = 0;i < n;i++){
int j = (i+1)% n;
int c = sgn(Cross(pt-p[j],p[i]-p[j]));
int u = sgn(p[i].y - pt.y);
int v = sgn(p[j].y - pt.y);
if(c > 0 && u < 0 && v >=0) num++;
if(c < 0 && u >=0 && v < 0) num--;
}
return num != 0; //1 内部; 0 外部
}
//求多边形的面积,从原点开始划分三角形,顶点按照顺时针或者逆时针方向排列
double Polygon_area(Point *p, int n){ //Point *p表示多边形。
double area = 0;
for(int i = 0;i < n;i++)
area += Cross(p[i],p[(i+1)%n]);
return area/2; //面积有正负,不能简单地取绝对值
}
//求多边形重心。Point *p表示多边形。
Point Polygon_center(Point *p, int n){
Point ans(0,0);
if(Polygon_area(p,n)==0) return ans;
for(int i = 0;i < n;i++)
ans = ans + (p[i]+p[(i+1)%n]) * Cross(p[i],p[(i+1)%n]); //面积有正负
return ans/Polygon_area(p,n)/6.;
}
//Convex_hull()求凸包。凸包顶点放在ch中,返回值是凸包的顶点数
int Convex_hull(Point *p,int n,Point *ch){
sort(p,p+n); //对点排序:按x从小到大排序,如果x相同,按y排序
n=unique(p,p+n)-p; //去除重复点
int v=0;
//求下凸包。如果p[i]是右拐弯的,这个点不在凸包上,往回退
for(int i=0;i<n;i++){
while(v>1 && sgn(Cross(ch[v-1]-ch[v-2],p[i]-ch[v-2]))<=0)
v--;
ch[v++]=p[i];
}
int j=v;
//求上凸包
for(int i=n-2;i>=0;i--){
while(v>j && sgn(Cross(ch[v-1]-ch[v-2],p[i]-ch[v-2]))<=0)
v--;
ch[v++]=p[i];
}
if(n>1) v--;
return v; //返回值v是凸包的顶点数
}
//---------------平面几何:圆----------------
struct Circle{
Point c;//圆心
double r;//半径
Circle(){}
Circle(Point c,double r):c(c),r(r){}
Circle(double x,double y,double _r){c=Point(x,y);r = _r;}
};
//点和圆的关系: 0 点在圆内, 1 圆上, 2 圆外.
int Point_circle_relation(Point p, Circle C){
double dst = Distance(p,C.c);
if(sgn(dst - C.r) < 0) return 0; //点在圆内
if(sgn(dst - C.r) ==0) return 1; //圆上
return 2; //圆外
}
//直线和圆的关系:0 直线在圆内, 1 直线和圆相切, 2 直线在圆外
int Line_circle_relation(Line v,Circle C){
double dst = Dis_point_line(C.c,v);
if(sgn(dst-C.r) < 0) return 0; //直线在圆内
if(sgn(dst-C.r) ==0) return 1; //直线和圆相切
return 2; //直线在圆外
}
//线段和圆的关系:0 线段在圆内, 1 线段和圆相切, 2 线段在圆外
int Seg_circle_relation(Segment v,Circle C){
double dst = Dis_point_seg(C.c,v);
if(sgn(dst-C.r) < 0) return 0; //线段在圆内
if(sgn(dst-C.r) ==0) return 1; //线段和圆相切
return 2; //线段在圆外
}
//直线和圆的交点,pa, pb是交点。返回值是交点个数
int Line_cross_circle(Line v,Circle C,Point &pa,Point &pb){
if(Line_circle_relation(v, C)==2) return 0;//无交点
Point q = Point_line_proj(C.c,v); //圆心在直线上的投影点
double d = Dis_point_line(C.c,v); //圆心到直线的距离
double k = sqrt(C.r*C.r-d*d); //
if(sgn(k) == 0){ //1个交点,直线和圆相切
pa = q;
pb = q;
return 1;
}
Point n=(v.p2-v.p1)/ Len(v.p2-v.p1); //单位向量
pa = q + n*k;
pb = q - n*k;
return 2;//2个交点
}
//-------------------三维几何----------------
//三维:点
struct Point3{
double x,y,z;
Point3(){}
Point3(double x,double y,double z):x(x),y(y),z(z){}
Point3 operator + (Point3 B){return Point3(x+B.x,y+B.y,z+B.z);}
Point3 operator - (Point3 B){return Point3(x-B.x,y-B.y,z-B.z);}
Point3 operator * (double k){return Point3(x*k,y*k,z*k);}
Point3 operator / (double k){return Point3(x/k,y/k,z/k);}
bool operator == (Point3 B){return sgn(x-B.x)==0 && sgn(y-B.y)==0 && sgn(z-B.z)==0;}
};
typedef Point3 Vector3;
//点积。和二维点积函数同名。C++允许函数同名。
double Dot(Vector3 A,Vector3 B){return A.x*B.x+A.y*B.y+A.z*B.z;}
//叉积
Vector3 Cross(Vector3 A,Vector3 B){return Point3(A.y*B.z-A.z*B.y,A.z*B.x-A.x*B.z,A.x*B.y-A.y*B.x);}
double Len(Vector3 A){return sqrt(Dot(A,A));} //向量的长度
double Len2(Vector3 A){return Dot(A,A);} //向量长度的平方
double Distance(Point3 A,Point3 B){
return sqrt((A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y)+(A.z-B.z)*(A.z-B.z));
}
double Angle(Vector3 A,Vector3 B){return acos(Dot(A,B)/Len(A)/Len(B));} //A与B的夹角
//三维:线
struct Line3{
Point3 p1,p2;
Line3(){}
Line3(Point3 p1,Point3 p2):p1(p1),p2(p2){}
};
typedef Line3 Segment3; //定义线段,两端点是Point p1,p2
//三角形面积的2倍
double Area2(Point3 A,Point3 B,Point3 C){return Len(Cross(B-A, C-A));}
//三维:点到直线距离
double Dis_point_line(Point3 p, Line3 v){
return Len(Cross(v.p2-v.p1,p-v.p1))/Distance(v.p1,v.p2);
}
//三维:点在直线上
bool Point_line_relation(Point3 p,Line3 v){
return sgn( Len(Cross(v.p1-p,v.p2-p))) == 0 && sgn(Dot(v.p1-p,v.p2-p))== 0;
}
//三维:点到线段距离。
double Dis_point_seg(Point3 p, Segment3 v){
if(sgn(Dot(p- v.p1,v.p2-v.p1)) < 0 || sgn(Dot(p- v.p2,v.p1-v.p2)) < 0)
return min(Distance(p,v.p1),Distance(p,v.p2));
return Dis_point_line(p,v);
}
//三维:点 p 在直线上的投影
Point3 Point_line_proj(Point3 p, Line3 v){
double k=Dot(v.p2-v.p1,p-v.p1)/Len2(v.p2-v.p1);
return v.p1+(v.p2-v.p1)*k;
}
//三维:平面
struct Plane{
Point3 p1,p2,p3;//平面上的三个点
Plane(){}
Plane(Point3 p1,Point3 p2,Point3 p3):p1(p1),p2(p2),p3(p3){}
};
//平面法向量
Point3 Pvec(Point3 A,Point3 B,Point3 C){ return Cross(B-A,C-A);}
Point3 Pvec(Plane f){ return Cross(f.p2-f.p1,f.p3-f.p1);}
//四点共平面
bool Point_on_plane(Point3 A,Point3 B,Point3 C,Point3 D){
return sgn(Dot(Pvec(A,B,C),D-A)) == 0;
}
//两平面平行
int Parallel(Plane f1, Plane f2){
return Len(Cross(Pvec(f1),Pvec(f2))) < eps;
}
//两平面垂直
int Vertical (Plane f1, Plane f2){
return sgn(Dot(Pvec(f1),Pvec(f2)))==0;
}
//直线与平面的交点p,返回值是交点个数
int Line_cross_plane(Line3 u,Plane f,Point3 &p){
Point3 v = Pvec(f);
double x = Dot(v, u.p2-f.p1);
double y = Dot(v, u.p1-f.p1);
double d = x-y;
if(sgn(x) == 0 && sgn(y) == 0) return -1;//-1:v在f上
if(sgn(d) == 0)return 0; //0:v与f平行
p = ((u.p1 * x)-(u.p2 * y))/d; //v与f相交
return 1;
}
//四面体有向体积*6
double volume4(Point3 A,Point3 B,Point3 C,Point3 D){return Dot(Cross(B-A,C-A),D-A);}
int main(){
Point a(0,1),b(0,0),c(1,1),d(1,2),p(1.5,1);
double a1=5,b1=6,c1=1;
Line k(a,b),k2(c,d);
Point pr(1,1),cr(1,1); double r=1; Circle C(cr,r);
cout<<endl<<"Line_circle_relation="<<Line_circle_relation(k,C);
cout<<endl<<"Seg_circle_relation="<<Seg_circle_relation(k,C);
cout<<endl<<"Point_circle_relation="<<Point_circle_relation(pr,C);
cout<<endl<<"parallel="<<Parallel(a,b)<<endl;
cout<<"dot="<<Dot(a,b)<<endl<<" angle="<<Angle(a,b)<<endl;
cout<<"90:"<<sgn(Rotate(a, -pi/2).x)<<endl<<Rotate(a, -pi/2).y;
cout<<endl<<"line angle="<<Line_angle(k)*4;
cout<<endl<<"line place="<<Point_line_relation(p,k);
cout<<endl<<"point_on_seg="<<Point_on_seg(p,k);
cout<<endl<<"dis_point_line="<<Dis_point_line(p,k);
cout<<endl<<"dis_point_line="<<Dis_point_seg(p,k);
Point pp=Cross_point(a,b,c,d);
cout<<endl<<"crosspoint="<<pp.x<<" "<<pp.y;
cout<<endl<<"cross seg="<<Cross_segment(a,b,c,d);
cout<<endl<<"distance="<<Distance(a,b);
cout<<endl<<"line_relation="<<Line_relation(k,k2);
Point g[4];
g[0]=a;g[1]=b;g[2]=c;g[3]=d;
cout<<endl<<"Point_in_polygon="<<Point_in_polygon(p,g,4);
cout<<endl<<"Polygon_area="<<Polygon_area(g,4);
cout<<endl<<endl;
return 0;
}
半平面交
点向式求半平面交
#include <bits/stdc++.h>
using namespace std;
const double INF = 1e12;
const double pi = acos(-1.0);
const double eps = 1e-8;
int sgn(double x){
if(fabs(x) < eps) return 0;
else return x<0?-1:1;
}
struct Point{
double x,y;
Point(){}
Point(double x,double y):x(x),y(y){}
Point operator + (Point B){return Point(x+B.x,y+B.y);}
Point operator - (Point B){return Point(x-B.x,y-B.y);}
Point operator * (double k){return Point(x*k,y*k);}
};
typedef Point Vector;
double Cross(Vector A,Vector B){return A.x*B.y - A.y*B.x;} //叉积
struct Line{
Point p;
Vector v;
double ang;
Line(){};
Line(Point p,Vector v):p(p),v(v){ang=atan2(v.y,v.x);}
bool operator < (Line &L){return ang<L.ang;} //用于极角排序
};
//点p在线L左边,即点p在线L在外面:
bool OnLeft(Line L,Point p){return sgn(Cross(L.v,p-L.p))>0;}
Point Cross_point(Line a,Line b){ //两直线交点
Vector u=a.p-b.p;
double t=Cross(b.v,u)/Cross(a.v,b.v);
return a.p+a.v*t;
}
vector<Point> HPI(vector<Line> L){ //求半平面交,返回图多边形
int n=L.size();
sort(L.begin(),L.end());//将所有半平面按照极角排序。
int l,r; //指向双端队列的第一个和最后一个元素
vector<Point> p(n); //两个相邻半平面的交点
vector<Line> q(n); //双端队列
vector<Point> ans; //半平面交形成的凸包
q[l=r=0]=L[0];
for(int i=1;i<n;i++){
//情况1:删除尾部的半平面
while(l<r && !OnLeft(L[i], p[r-1])) r--;
//情况2:删除首部的半平面:
while(l<r && !OnLeft(L[i], p[l])) l++;
q[++r]=L[i]; //将当前的半平面加入双端队列尾部
//极角相同的两个半平面,保留左边:
if(fabs(Cross(q[r].v,q[r-1].v)) < eps){
r--;
if(OnLeft(q[r],L[i].p)) q[r]=L[i];
}
//计算队列尾部半平面交点:
if(l<r) p[r-1]=Cross_point(q[r-1],q[r]);
}
//情况3:删除队列尾部的无用半平面
while(l<r && !OnLeft(q[l],p[r-1])) r--;
if(r-l<=1) return ans; //空集
p[r]=Cross_point(q[r],q[l]); //计算队列首尾部的交点。
for(int i=l;i<=r;i++) ans.push_back(p[i]); //复制。
return ans; //返回凸多边形
}
int main(){
int T,n;
cin>>T;
while(T--){
cin>>n;
vector<Line> L;
//加一个半平面F:反向y轴
L.push_back(Line(Point(0,0),Vector(0,-1)));
//加一个半平面E:y极大的向左的直线
L.push_back(Line(Point(0,INF),Vector(-1,0)));
while(n--){
double a,b;
scanf("%lf%lf",&a,&b);
L.push_back(Line(Point(0,a),Vector(1,b)));
}
vector<Point> ans=HPI(L); //得到凸多边形
printf("%d\n",ans.size()-2); //去掉人为加的两个点
}
return 0;
}
两点式求半平面交一
const double INF = 1e12;
const double eps = 1e-8;
int n,m;
int sgn(double x){ //判断x是否等于0
if(fabs(x) < eps) return 0;
else return x<0?-1:1;
}
struct Point{ //定义点和基本运算
double x,y;
Point(){}
Point(double x,double y):x(x),y(y){}
Point operator + (Point B){return Point(x+B.x,y+B.y);}
Point operator - (Point B){return Point(x-B.x,y-B.y);}
Point operator * (double k){return Point(x*k,y*k);} //长度增大k倍
Point operator / (double k){return Point(x/k,y/k);} //长度缩小k倍
};
typedef Point Vector; //定义向量
//点积
double Dot(Vector A,Vector B){return A.x*B.x + A.y*B.y;}
double Dist(Point A,Point B){return sqrt((A.x-B.x)*(A.x-B.x) + (A.y-B.y)*(A.y-B.y));}
double Cross(Vector A,Vector B){return A.x*B.y - A.y*B.x;}
double Area2(Point A, Point B, Point C){return Cross(B-A, C-A);}
struct Line{
Point p1,p2;//线上的两个点
Line(){}
Line(Point p1,Point p2):p1(p1),p2(p2){}
};
//得到极角角度
double get_angle(Line a) {
return atan2(a.p2.y-a.p1.y,a.p2.x-a.p1.x);
}
//排序:极角小的排前面,极角相同时,最左边的排在最后面,以便去重
bool cmp(Line a, Line b) {
double A = get_angle(a), B = get_angle(b);
if (fabs(A - B) < eps) return Area2(a.p1,a.p2,b.p2)<0;
return A < B;
}
//点和直线关系:1 在左侧;2 在右侧;0 在直线上
int Point_line_relation(Point p,Line v){
int c = sgn(Cross(p-v.p1,v.p2-v.p1));
if(c < 0)return 1; //1:p在v的左边
if(c > 0)return 2; //2:p在v的右边
return 0; //0:p在v上
}
//求两直线的交点
Point Cross_point(Point a,Point b,Point c,Point d){ //Line1:ab, Line2:cd
double s1 = Cross(b-a,c-a);
double s2 = Cross(b-a,d-a); //叉积有正负
return Point(c.x*s2-d.x*s1,c.y*s2-d.y*s1)/(s2-s1);
}
//两直线交点
Point Cross_point(Line a,Line b){
return Cross_point(a.p1,a.p2,b.p1,b.p2);
}
bool OnLeft(Line l,Point p){
if(Point_line_relation(p,l) == 1) return true;
return false;
}
//求半平面交,返回图多边形
vector<Point> HPI(vector<Line> L){
int n=L.size();
sort(L.begin(),L.end(),cmp);//将所有半平面按照极角排序。
int l,r; //指向双端队列的第一个和最后一个元素
vector<Point> p(n); //两个相邻半平面的交点
vector<Point> ans; //半平面交形成的凸包
vector<Line> q(n); //双端队列
q[l=r=0]=L[0];
for(int i=1;i<n;i++){
//情况1:删除尾部的半平面
while(l<r && !OnLeft(L[i], p[r-1])) r--;
//情况2:删除首部的半平面:
while(l<r && !OnLeft(L[i], p[l])) l++;
q[++r]=L[i]; //将当前的半平面加入双端队列尾部
//极角相同的两个半平面,保留左边:
if(fabs(Cross(q[r].p2-q[r].p1,q[r-1].p2-q[r-1].p1)) < eps){
r--;
if(OnLeft(q[r],L[i].p1)) q[r]=L[i];
}
//计算队列尾部半平面交点:
if(l<r) p[r-1]=Cross_point(q[r-1],q[r]);
}
//情况3:删除队列尾部的无用半平面
while(l<r && !OnLeft(q[l],p[r-1])) r--;
if(r-l<=1) return ans; //空集
p[r]=Cross_point(q[r],q[l]); //计算队列首尾部的交点。
for(int i=l;i<=r;i++) ans.push_back(p[i]); //复制
return ans; //返回凸多边形
}
vector<Line> L;
vector<Point> ans;
int main(){
int i,j,T;
cin>>T;
while(T--){
int n;
L.clear();
cin>>n;
for(i = 1;i<=n;i++){
double p,v;
scanf("%lf %lf",&p,&v);
Line l(Point(0,p),Point(1,v+p));
L.push_back(l);
}
//加入两个边界的直线
L.push_back(Line(Point(0,0),Point(0,-1)));
L.push_back(Line(Point(0,INF),Point(-1,INF)));
vector<Point> ans = HPI(L);
cout<<ans.size()-2<<endl;
}
return 0;
}
两点式求半平面交模板二
typedef long long ll;
typedef long double LD;
typedef pair<int,int> PII;
const double inf = 1e12;
const int N = 100005;
const LD eps = 1e-18;
int n,m;
int sgn(double x){ //判断x是否等于0
if(fabs(x) < eps) return 0;
else return x<0?-1:1;
}
int Dcmp(double x, double y){ //比较两个浮点数:0 相等;-1 小于;1 大于
if(fabs(x - y) < eps) return 0;
else return x<y ?-1:1;
}
struct Point{ //定义点和基本运算
double x,y;
Point(){}
Point(double x,double y):x(x),y(y){}
Point operator + (Point B){return Point(x+B.x,y+B.y);}
Point operator - (Point B){return Point(x-B.x,y-B.y);}
Point operator * (double k){return Point(x*k,y*k);} //长度增大k倍
Point operator / (double k){return Point(x/k,y/k);} //长度缩小k倍
bool operator == (Point B){return sgn(x-B.x)==0 && sgn(y-B.y)==0;}
bool operator < (Point B){return sgn(x-B.x)<0 || (sgn(x-B.x)==0 && sgn(y-B.y)<0);} //用于凸包
};
typedef Point Vector; //定义向量
double Cross(Vector A,Vector B){return A.x*B.y - A.y*B.x;}
double Area2(Point A, Point B, Point C){return Cross(B-A, C-A);}
struct Line{
Point p1,p2;//线上的两个点
Line(){}
Line(Point p1,Point p2):p1(p1),p2(p2){}
};
Line line[N];
int cnt;
int q[N];//定义双端队列
double get_angle(Line a) {
return atan2(a.p2.y-a.p1.y,a.p2.x-a.p1.x);
}
//排序:极角小的排前面,极角相同时,最左边的排在最后面,以便去重
bool cmp(Line a, Line b) {
double A = get_angle(a), B = get_angle(b);
if (fabs(A - B) < eps) return Area2(a.p1,a.p2,b.p2)<0;
return A < B;
}
Point Cross_point(Point a,Point b,Point c,Point d){ //Line1:ab, Line2:cd
double s1 = Cross(b-a,c-a);
double s2 = Cross(b-a,d-a); //叉积有正负
return Point(c.x*s2-d.x*s1,c.y*s2-d.y*s1)/(s2-s1);
}
//两直线交点
Point Cross_point(Line a,Line b){
return Cross_point(a.p1,a.p2,b.p1,b.p2);
}
// 判断bc的交点是否在a的右侧
bool on_right(Line a, Line b, Line c){
Point o = Cross_point(b, c);
//如果删除重合的点加上等号
return Area2(a.p1, a.p2, o) <= 0;
}
vector<Point> HPI(vector<Line> L){
int cnt = L.size();
sort(L.begin(),L.end(), cmp);
int l = 0, r = -1;//头部和尾部
for (int i = 0; i < cnt; i ++ ){
if (i && !Dcmp(get_angle(L[i - 1]), get_angle(L[i]))) continue;
while (l + 1 <= r && on_right(L[i], L[q[r - 1]], L[q[r]])) r -- ;
while (l + 1 <= r && on_right(L[i], L[q[l]], L[q[l + 1]])) l ++ ;
q[ ++ r] = i;
}
while (l + 1 <= r && on_right(L[q[l]], L[q[r - 1]], L[q[r]])) r -- ;
while (l + 1 <= r && on_right(L[q[r]], L[q[l]], L[q[l + 1]])) l ++ ;
int k = 0;
vector<Point> ans;
if(r - l <= 1) return ans;//返回空集
q[++r] = q[l];//将头部的直线加入尾部
for(int i = l;i<r;i++){
ans.push_back(Cross_point(L[q[i]],L[q[i+1]]));
}
return ans;
}
double Polygon_area(vector<Point> p){ //Point *p表示多边形。
double area = 0;
int n = p.size();
for(int i = 0;i < n;i++)
area += Cross(p[i],p[(i+1)%n]);
return area/2; //面积有正负,不能简单地取绝对值
}
vector<Line> L;
vector<Point> ans;
Point pg[N];
int main(){
int i,j,T;
cin>>T;
while(T--){
int n;
L.clear();
cin>>n;
for(i = 1;i<=n;i++){
double p,v;
scanf("%lf %lf",&p,&v);
Line l(Point(0,p),Point(1,v+p));
L.push_back(l);
}
//加入两个边界的直线
L.push_back(Line(Point(0,0),Point(0,-1)));
L.push_back(Line(Point(0,inf),Point(-1,inf)));
vector<Point> ans = HPI(L);
cout<<ans.size()-2<<endl;
}
return 0;
}
N圆面积并&&交
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef vector <int> VI;
const int INF = 0x3f3f3f3f;
const double eps = 1e-10;
const int MOD = 100000007;
const int MAXN = 1000010;
const double PI = acos(-1.0);
#define sqr(x) ((x)*(x))
const int N = 1010;
double area[N];
int n;
int dcmp(double x){
if (x < -eps) return -1;
else return x > eps;
}
struct cp{
double x, y, r, angle;
int d;
cp() {}
cp(double xx, double yy, double ang = 0, int t = 0){
x = xx;
y = yy;
angle = ang;
d = t;
}
void get(){
scanf("%lf%lf%lf", &x, &y, &r);
d = 1;
}
} cir[N], tp[N * 2];
double dis(cp a, cp b){return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y));}
double cross(cp p0, cp p1, cp p2){return (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x);}
int CirCrossCir(cp p1, double r1, cp p2, double r2, cp &cp1, cp &cp2){
double mx = p2.x - p1.x, sx = p2.x + p1.x, mx2 = mx * mx;
double my = p2.y - p1.y, sy = p2.y + p1.y, my2 = my * my;
double sq = mx2 + my2, d = -(sq - sqr(r1 - r2)) * (sq - sqr(r1 + r2));
if (d + eps < 0) return 0;
if (d < eps) d = 0;
else d = sqrt(d);
double x = mx * ((r1 + r2) * (r1 - r2) + mx * sx) + sx * my2;
double y = my * ((r1 + r2) * (r1 - r2) + my * sy) + sy * mx2;
double dx = mx * d, dy = my * d;
sq *= 2;
cp1.x = (x - dy) / sq;
cp1.y = (y + dx) / sq;
cp2.x = (x + dy) / sq;
cp2.y = (y - dx) / sq;
if (d > eps) return 2;
else return 1;
}
bool circmp(const cp& u, const cp& v){return dcmp(u.r - v.r) < 0;}
bool cmp(const cp& u, const cp& v){
if (dcmp(u.angle - v.angle)) return u.angle < v.angle;
return u.d > v.d;
}
double calc(cp cir, cp cp1, cp cp2){
double ans = (cp2.angle - cp1.angle) * sqr(cir.r)
- cross(cir, cp1, cp2) + cross(cp(0, 0), cp1, cp2);
return ans / 2;
}
void CirUnion(cp cir[], int n){
cp cp1, cp2;
sort(cir, cir + n, circmp);
for (int i = 0; i < n; ++i)
for (int j = i + 1; j < n; ++j)
if (dcmp(dis(cir[i], cir[j]) + cir[i].r - cir[j].r) <= 0)
cir[i].d++;
for (int i = 0; i < n; ++i){
int tn = 0, cnt = 0;
for (int j = 0; j < n; ++j){
if (i == j) continue;
if (CirCrossCir(cir[i], cir[i].r, cir[j], cir[j].r,cp2, cp1) < 2) continue;
cp1.angle = atan2(cp1.y - cir[i].y, cp1.x - cir[i].x);
cp2.angle = atan2(cp2.y - cir[i].y, cp2.x - cir[i].x);
cp1.d = 1;
tp[tn++] = cp1;
cp2.d = -1;
tp[tn++] = cp2;
if (dcmp(cp1.angle - cp2.angle) > 0) cnt++;
}
tp[tn++] = cp(cir[i].x - cir[i].r, cir[i].y, PI, -cnt);
tp[tn++] = cp(cir[i].x - cir[i].r, cir[i].y, -PI, cnt);
sort(tp, tp + tn, cmp);
int p, s = cir[i].d + tp[0].d;
for (int j = 1; j < tn; ++j){
p = s;
s += tp[j].d;
area[p] += calc(cir[i], tp[j - 1], tp[j]);
}
}
}
void solve(){
scanf("%d", &n);
double sum = 0;
for (int i = 0; i < n; ++i)cir[i].get();
memset(area, 0, sizeof(area));
CirUnion(cir, n);
for (int i = 1; i <= n; ++i) //去掉重复计算的
area[i] -= area[i + 1]; //area[i]为重叠了i次的面积
double tot = 0;//tot 为总面积
for(int i=1; i<=n; i++) tot += area[i];
// cout<<area[n]<<endl; //重叠了n次就是所求N圆相交面积
printf("%f\n", tot); //这句就是求总面积
}
int main(){
solve();
return 0;
}
/*
in
3
1 0 2
-1 0 2
0 1 2
Out
4.20324 相交面积
//22.929505 相并面积
*/
凸包&判凸包相交
#include<cstdio>
#include<iostream>
#include<vector>
#include<cmath>
#include<algorithm>
using namespace std;
const double eps = 1e-10;
double dcmp(double x) {
if(fabs(x) < eps) return 0;
else return x < 0 ? -1 : 1;
}
struct Point {
double x, y;
Point(double x=0, double y=0):x(x),y(y) {}
};
typedef Point Vector;
Vector operator - (const Point& A, const Point& B) {
return Vector(A.x-B.x, A.y-B.y);
}
double Cross(const Vector& A, const Vector& B) {
return A.x*B.y - A.y*B.x;
}
double Dot(const Vector& A, const Vector& B) {
return A.x*B.x + A.y*B.y;
}
bool operator < (const Point& p1, const Point& p2) {
return p1.x < p2.x || (p1.x == p2.x && p1.y < p2.y);
}
bool operator == (const Point& p1, const Point& p2) {
return p1.x == p2.x && p1.y == p2.y;
}
bool SegmentProperIntersection(const Point& a1, const Point& a2, const Point& b1, const Point& b2) {
double c1 = Cross(a2-a1,b1-a1), c2 = Cross(a2-a1,b2-a1),
c3 = Cross(b2-b1,a1-b1), c4=Cross(b2-b1,a2-b1);
return dcmp(c1)*dcmp(c2)<0 && dcmp(c3)*dcmp(c4)<0;
}
bool OnSegment(const Point& p, const Point& a1, const Point& a2) {
return dcmp(Cross(a1-p, a2-p)) == 0 && dcmp(Dot(a1-p, a2-p)) < 0;
}
// 点集凸包
// 如果不希望在凸包的边上有输入点,把两个 <= 改成 <
// 如果不介意点集被修改,可以改成传递引用
vector<Point> ConvexHull(vector<Point> p) {
// 预处理,删除重复点
sort(p.begin(), p.end());
p.erase(unique(p.begin(), p.end()), p.end());
int n = p.size();
int m = 0;
vector<Point> ch(n+1);
for(int i = 0; i < n; i++) {
while(m > 1 && Cross(ch[m-1]-ch[m-2], p[i]-ch[m-2]) <= 0) m--;
ch[m++] = p[i];
}
int k = m;
for(int i = n-2; i >= 0; i--) {
while(m > k && Cross(ch[m-1]-ch[m-2], p[i]-ch[m-2]) <= 0) m--;
ch[m++] = p[i];
}
if(n > 1) m--;
ch.resize(m);
return ch;
}
//点是否在凸包内
int IsPointInPolygon(const Point& p, const vector<Point>& poly) {
int wn = 0;
int n = poly.size();
for(int i=0; i<n; ++i) {
const Point& p1 = poly[i];
const Point& p2 = poly[(i+1)%n];
if(p1 == p || p2 == p || OnSegment(p, p1, p2)) return -1;//在边界上
int k = dcmp(Cross(p2-p1, p-p1));
int d1 = dcmp(p1.y - p.y);
int d2 = dcmp(p2.y - p.y);
if(k > 0 && d1 <= 0 && d2 > 0) wn++;
if(k < 0 && d2 <= 0 && d1 > 0) wn--;
}
if(wn != 0) return 1;
return 0;
}
//判断2个凸包是否相交
bool ConvexPolygonDisjoint(const vector<Point> ch1, const vector<Point> ch2) {
int c1 = ch1.size();
int c2 = ch2.size();
for(int i=0; i<c1; ++i)
if(IsPointInPolygon(ch1[i], ch2) != 0) return false;
for(int i=0; i<c2; ++i)
if(IsPointInPolygon(ch2[i], ch1) != 0) return false;
for(int i=0; i<c1; ++i)
for(int j=0; j<c2; ++j)
if(SegmentProperIntersection(ch1[i], ch1[(i+1)%c1], ch2[j], ch2[(j+1)%c2])) return false;
return true;
}
int main() {
int n, m;
int t;
scanf("%d%d", &n,&m);
vector<Point> P1, P2;
double x, y;
int ww;
for(int i = 0; i < n; i++) {
scanf("%lf%lf", &x, &y);
P1.push_back(Point(x, y));
}
for(int i = 0;i < m; ++i) {
scanf("%lf%lf", &x, &y);
P2.push_back(Point(x, y));
}
if(P1.size()==0||P2.size()==0){
printf("NO\n");
return 0;
}
if(ConvexPolygonDisjoint(ConvexHull(P1), ConvexHull(P2)))
printf("NO\n");
else
printf("YES\n");
return 0;
}
其他
快读快输模板
有些题目可能会丧心病狂的卡常,这个时候我们可以使用快读快输这种方法优化我们程序的常数。
/*
作者:Mangata
int的快读快输
*/
#include<cstdio>
inline int read()
{
int X=0; bool flag=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
if(flag) return X;
return ~(X-1);
}
inline int write(int X)
{
if(X<0) {putchar('-'); X=~(X-1);}
int s[20],top=0;
while(X) {s[++top]=X%10; X/=10;}
if(!top) s[++top]=0;
while(top) putchar(s[top--]+'0');
}
int main()
{
while(1){
int n = read();
write(n);
putchar('\n');
}
return 0;
}
_int128模板
能够存储128位2进制的数(算是微大数)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline void scan(__int128 &x)//输入
{
x = 0;
int f = 1;
char ch;
if((ch = getchar()) == '-') f = -f;
else x = x*10 + ch-'0';
while((ch = getchar()) >= '0' && ch <= '9')
x = x*10 + ch-'0';
x *= f;
}
inline void print(__int128 x) {
if(x < 0) {
x = -x;
putchar('-');
}
if(x > 9)
print(x/10);
putchar(x % 10 + '0');
}
int main()
{
__int128 a,b,c;
scan(a);
scan(b);
c = a+b;
print(c);
return 0;
}
二分模板
二分的模板有很多,最好记住并使用一种二分模板就行,这是Mangata最爱的二分模板
/*
作者:Mangata
二分模板返回大于x的第一个位置
*/
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 1005
using namespace std;
int a[N],n,q;
int find(int l,int r,int key)//l为-1,r为数组长度
{
while(l + 1 < r)
{
int mid = l + r>>1;
if(a[mid] <= key)
l = mid;
else
r = mid;
}
return r;//返回大于Key的第一个位置
}
int main()
{
int k;
scanf("%d%d",&n,&q);
for(int i = 0; i < n; ++i)
scanf("%d",&a[i]);
for(int i = 0; i < q; ++i)
{
scanf("%d",&k);
printf("%d\n",find(-1,n,k));
}
}
排序
快速排序
void quick_sort(int l,int r){
if(l == r) return;
int x = a[(l+r)/2];
int l1 = l-1,r1 = r+1;
while(l1<r1){
do l1++;
while(a[l1]<x);
do r1--;
while(a[r1]>x);
if(l1<r1) swap(a[l1],a[r1]);
}
quick_sort(l,r1);
quick_sort(r1+1,r);
}
归并排序
void merge_sort(int l,int r){
if(l==r)return;
int m = (l+r)/2;
merge_sort(l,m);
merge_sort(m+1,r);
int i = l,j = m+1;
int k = i;
while(i<=m&&j<=r){
if(a[i]<a[j]) tmp[k++] = a[i++];
else tmp[k++] = a[j++];
}
while(i<=m)tmp[k++] = a[i++];
while(j<=r)tmp[k++] = a[j++];
for(i = l;i<=r;i++)
a[i] = tmp[i];
}
归并排序求逆序对
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int a[N],tmp[N];
long long merge_sort(int a[],int l,int r){
if(l>=r)
return 0;
int mid=l+r>>1,i=l,j=mid+1,k=0;
long long res=merge_sort(a,l,mid)+merge_sort(a,mid+1,r);
while(i<=mid&&j<=r){
if(a[i]<=a[j])
tmp[k++]=a[i++];
else{
tmp[k++]=a[j++];
res+=mid-i+1;
}
}
while(i<=mid) tmp[k++]=a[i++];
while(j<=r) tmp[k++]=a[j++];
for(int i=l,j=0;i<=r;i++,j++)
a[i]=tmp[j];
return res;
}
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
cout<<merge_sort(a,0,n-1);
return 0;
}
JAVA高精度
import java.util.*;
import java.io.BufferedInputStream;
import java.math.*;
public class Main {
static int N = 2005;
static BigInteger mod = new BigInteger("1000000007");
static int n;
static BigInteger[] a = new BigInteger[N];
static BigInteger[] dp = new BigInteger[N];
public static void main(String args[]) {
Scanner cin = new Scanner(System.in);
int T;
BigInteger a = new BigInteger("123456789") ; // 声明BigInteger对象
BigInteger b = new BigInteger("987654321") ; // 声明BigInteger对象
System.out.println("加法操作:" + b.add(a)) ; // 加法操作
System.out.println("减法操作:" + b.subtract(a)) ; // 减法操作
System.out.println("乘法操作:" + b.multiply(a)) ; // 乘法操作
System.out.println("除法操作:" + b.divide(a)) ; // 除法操作
System.out.println("最大数:" + b.max(a)) ; // 求出最大数
System.out.println("最小数:" + b.min(a)) ; // 求出最小数
System.out.println("商:"+b.divide(a));//求商
System.out.println("余数"+b.mod(a));//求余数
BigInteger result[] = b.divideAndRemainder(a) ; // 求出余数的除法操作
System.out.println("商是:" + result[0] +
";余数是:" + result[1]) ;
//比较函数
if( a.compareTo(b) == 0 ) System.out.println("a == b"); //大整数a==b
else if( a.compareTo(b) > 0 ) System.out.println("a > b"); //大整数a>b
else if( a.compareTo(b) < 0 ) System.out.println("a < b"); //大整数a<b
int n;
BigInteger m;
n=cin.nextInt(); //读入一个int;
m=cin.nextBigInteger();//读入一个BigInteger;
m=BigInteger.valueOf(n);//将n转换成大整数
}
}
坐标离散化
例一
步骤
具体的离散化操作分为三部分
1. 排序
2. 去重(去掉相邻的重复元素)
3. 坐标离散化处理(最关键的!!!详情请参见代码)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
struct Node{
int x,y;
};
const int N = 505;
bool mp[N * 6][N * 6];
int dx[4] = {-1,1,0,0},dy[4] = {0,0,-1,1};
int X1[N * 6],Y1[N * 6],X2[N * 6],Y2[N * 6];
int W,H,n;
//对x1和x2进行离散化处理,并且返回离散后的宽度
int compress(int *x1,int *x2,int w) {
vector<int> xs;
//先将所有x坐标放进xs数组中
for(int i = 0; i < n; ++i) {
for(int d = -1; d <= 1; ++d) {
int tx1 = x1[i] + d;
int tx2 = x2[i] + d;
if(1 <= tx1 && tx1 <= w) xs.push_back(tx1);
if(1 <= tx2 && tx2 <= w) xs.push_back(tx2);
}
}
//1.排序
sort(xs.begin(),xs.end());
//2.去重 因为unique函数返回值是去掉相邻重复元素后的尾部的位置
// (他会把重复的元素添加到尾部,所以元素的个数还是没变)
xs.erase((unique(xs.begin(),xs.end())),xs.end());
//3.离散化,将大空间的下标投射到小范围中
for(int i = 0; i < n; ++i) {
x1[i] = find(xs.begin(),xs.end(),x1[i]) - xs.begin();//当然这里也可以自己写一个二分函数
x2[i] = find(xs.begin(),xs.end(),x2[i]) - xs.begin();//貌似更快
}
return xs.size();//返回的是离散后的宽或者长
}
void bfs(int x,int y) {
queue<Node> que;
que.push({x,y});
mp[x][y] = true;
while(!que.empty()) {
Node t = que.front();
que.pop();
for(int i = 0; i < 4; ++i) {
int nx = t.x + dx[i];
int ny = t.y + dy[i];
if(nx >= 0 && nx < H && ny >= 0 && ny < W && (!mp[nx][ny])) {
que.push({nx,ny});
mp[nx][ny] = true;
}
}
}
}
int main()
{
scanf("%d%d%d",&W,&H,&n);
for(int i = 0;i < n; ++i) scanf("%d",&X1[i]);
for(int i = 0;i < n; ++i) scanf("%d",&X2[i]);
for(int i = 0;i < n; ++i) scanf("%d",&Y1[i]);
for(int i = 0;i < n; ++i) scanf("%d",&Y2[i]);
W = compress(X1,X2,W);//列数
H = compress(Y1,Y2,H);//行数
//绘制地图
for(int i = 0; i < n; ++i) {
for(int y = Y1[i]; y <= Y2[i]; ++y) {
for(int x = X1[i]; x <= X2[i]; ++x) {
mp[y][x] = true;
}
}
}
for(int y = 0; y < H; ++y) {
for(int x = 0; x < W; ++x) {
printf("%d%c",mp[y][x],x==W-1?'\n':' ');
}
}
int ans = 0;
for(int i = 0; i < H; ++i) {
for(int j = 0; j < W; ++j) {
if(!mp[i][j]) {
ans++;
bfs(i,j);
}
}
}
printf("%d\n",ans);
return 0;
/*
10 10 5
1 1 4 9 10
6 10 4 9 10
4 8 1 1 6
4 8 10 5 10
*/
}
To Be Continue……