RMQ(区间最值查询问题)
问题描述
给定长度为N的序列,M个询问,每次询问两个数字A,B,要求求出属于A到B这段区间内的最大数
#include<cstdio>
#include<cstring>
#include<iostream>
#define maxn 200010
using namespace std;
int m,n,t[maxn],f[maxn][32],log[maxn];
void rmq()
{
for(int i=1;i<=n;i++)
f[i][0]=t[i];
for(int j=1;(1<<j)<=n;j++)
{
for(int i=1;i+(1<<(j-1))<=n;i++)
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
void getlog()
{
log[1]=0;
for(int i=2;i<=n+1;i++)
log[i]=log[i/2]+1;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&t[i]);
scanf("%d",&m);
rmq();
getlog();
while(m--)
{
int l,r;
scanf("%d%d",&l,&r);
int k=log[r-l+1];
int ans=max(f[l][k],f[r-(1<<k)+1][k]);
printf("%d\n",ans);
}
}
LCA(最近公共祖先)
问题描述
给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 500002
using namespace std;
struct zzz
{
int t,next;
}e[maxn<<1];
int head[maxn],tot;
int depth[maxn],fa[maxn][22],lg[maxn];//数组depth表示每个节点的深度,fa[i][j]表示节点i的2^j级祖先
void add(int u,int v)
{
e[++tot].t=v;
e[tot].next=head[u];
head[u]=tot;
}
void dfs(int now,int fath)
{
fa[now][0]=fath;depth[now]=depth[fath]+1;
for(int i=1;i<=lg[depth[now]];i++)
fa[now][i]=fa[fa[now][i-1]][i-1];
for(int i=head[now];i;i=e[i].next)
if(e[i].t!=fath) dfs(e[i].t,now);
}
int lca(int x,int y)
{
if(depth[x]<depth[y]) swap(x,y);
while(depth[x]>depth[y])
x=fa[x][lg[depth[x]-depth[y]]-1];
if(x==y) return x;
for(int k=lg[depth[x]]-1;k>=0;k--)
if(fa[x][k]!=fa[y][k])
x=fa[x][k],y=fa[y][k];
return fa[x][0];
}
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=n-1;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
for(int i=1;i<=n;i++)
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
dfs(s,0);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}
链式前向星的写法
代码
#include<bits/stdc++.h>
using namespace std;
#define MAXN 100501
struct NODE{
int w;
int to;
int next; //next[i]表示与第i条边同起点的上一条边的储存位置
}edge[MAXN];
int cnt;
int head[MAXN];
void add(int u,int v,int w){
edge[++cnt].w=w;
edge[cnt].to=v; //edge[i]表示第i条边的终点
edge[cnt].next=head[u]; //head[i]表示以i为起点的最后一条边的储存位置
head[u]=cnt;
}
int main(){
memset(head,0,sizeof(head));
int n;
cin>>n;
int a,b,c;
while(n--){
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);//无向图注意用两次
}
int start;
cin>>start;
for(int i=head[start];i!=0;i=edge[i].next)
cout<<start<<"->"<<edge[i].to<<" "<<edge[i].w<<endl;
return 0;
}
讲解
其中edge[i].to表示第i条边的终点,edge[i].next表示与第i条边同起点的下一条边的存储位置,edge[i].w为边权值.另外还有一个数组head[],它是用来表示以i为起点的第一条边存储的位置,实际上你会发现这里的第一条边存储的位置其实在以i为起点的所有边的最后输入的那个编号.
树形dp
最长链
题目描述
最长链为这棵二叉树中一条最长的简单路径,即不经过重复结点的一条路径。可以容易证明,二叉树中最长链的起始、结束结点均为叶子结点。
输入
输入第1行为包含了一个正整数N,为这棵二叉树的结点数,结点标号由1至N。
接下来N行,这N行中的第i行包含两个正整数l[i], r[i],表示了结点i的左儿子与右儿子编号。如果l[i]为0,表示结点i没有左儿子,同样地,如果r[i]为0则表示没有右儿子。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
#define maxn 100010
int l[maxn],r[maxn];
int g[maxn];
int f[maxn];
int tmax=-1;
void dfs(int x)
{
if(x!=0)
{
dfs(l[x]);
dfs(r[x]);
g[x]=max(g[l[x]],g[r[x]])+1;
}
else g[x]=0;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&l[i],&r[i]);
dfs(1);
for(int i=1;i<=n;i++)
{
f[i]=1+g[l[i]]+g[r[i]];
if(f[i]>tmax)
tmax=f[i];
}
printf("%d",tmax-1);
}
树上多源最长路
题目描述
给定一棵有边权的无向树(无向无环图
对于每个结点,求离他最远的那个结点到他的距离(要是简单路径,不能来回走)
#include <cstdio>
#include <cstring>
#define MAX(a, b) ((a)>(b)?(a):(b))
constexpr int MV(10002), ME(MV<<1);
struct Ed
{
int v,d;
Ed *next;
}ed[ME],*head[MV];
int tot;
#define edd(uu, vv, dd) ed[++tot].next=head[uu], ed[tot].v=vv, ed[tot].d=dd, head[uu]=ed+tot
int up[MV],down[MV];
int fa[MV];
void dfs1(const int u, const int f)
{
fa[u] = f;
down[u] = 0;
for (const Ed *p=head[u]; p; p=p->next)
{
const int v = p->v;
if (v != f)
{
dfs1(v, u); // 自底向上,先dfs再dp更新
if (down[u] < down[v]+p->d)
down[u] = down[v]+p->d;
}
}
}
void dfs2(const int u)
{
if (fa[u])
{
int max_fa_down = 0;
int d_u_fa;
for (const Ed *p=head[fa[u]]; p; p=p->next)
{
const int bro = p->v;
if (bro != fa[fa[u]])
{
if (bro == u)
d_u_fa = p->d;
else if (max_fa_down < down[bro]+p->d)
max_fa_down = down[bro]+p->d;
}
}
up[u] = d_u_fa + MAX(up[fa[u]], max_fa_down);
}
else
up[u] = 0;
for (const Ed *p=head[u]; p; p=p->next)
if (p->v != fa[u])
dfs2(p->v); // 自顶向下,先dp更新再dfs
}
int main()
{
int V;
while (~scanf("%d", &V))
{
tot = 0;
memset(head, 0, sizeof(*head) * (V+1));
int v, d;
for (int u=2; u<=V; ++u)
{
scanf("%d %d", &v, &d);
edd(u, v, d);
edd(v, u, d);
}
dfs1(1, 0);
dfs2(1);
for (int u=1; u<=V; ++u)
printf("%d\n", MAX(up[u], down[u]));
}
return 0;
}
没有上司的舞会
题目描述
某大学有N个职员,编号为1~N。他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数Ri,但是呢,如果某个职员的上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。
分析
f[x][0]表示以x为根的子树,且x不参加舞会的最大快乐值
f[x][1]表示以x为根的子树,且x参加了舞会的最大快乐值
则f[x][0]=sigma{max(f[y][0],f[y][1])} (y是x的儿子)
f[x][1]=sigma{f[y][0]}+h[x] (y是x的儿子)
先找到唯一的树根root
则ans=max(f[root][0],f[root][1])
输入
第一行一个整数N。(1<=N<=6000)
接下来N行,第i+1行表示i号职员的快乐指数Ri。(-128<=Ri<=127)
接下来N-1行,每行输入一对整数L,K。表示K是L的直接上司。
#include<cstdio>
#include<vector>
#include<iostream>
#include<cstring>
using namespace std;
#define MAXN 6005
int h[MAXN];
int v[MAXN];
vector<int> son[MAXN];
int f[MAXN][2];
void dp(int x)
{
f[x][0]=0;
f[x][1]=h[x];
for(int i=0;i<son[x].size();i++)
{
int y=son[x][i];
dp(y);
f[x][0]+=max(f[y][0],f[y][1]);
f[x][1]+=f[y][0];
}
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>h[i];
for(int i=1;i<=n-1;i++)
{
int x,y;
cin>>x>>y;
son[y].push_back(x);
v[x]=1;
}
int root;
for(int i=1;i<=n;i++)
if(!v[i]) {root=i;break;}
dp(root);
cout<<max(f[root][0],f[root][1])<<endl;
return 0;
}
二叉苹果树
题目描述
有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点)
这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1。
我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有4个树枝的树
2 5
\ /
3 4
\ /
1
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。
给定需要保留的树枝数量,求出最多能留住多少苹果。
分析
这道题有一个隐含的条件,当某条边被保留下来时,
从根节点到这条边的路径上的所有边也都必须保留下来
设f[u][i]表示u的子树上保留i条边
那么状态转移方程也就显而易见了
f[u][i]=max(f[u][i],f[u][i−j−1]+f[v][j]+e[i].w)
( 1≤i≤min(q,sz[u]),0≤j≤min(sz[v],i−1) )
u表示当前节点,v是u的一个子节点,
sz[u]表示u的子树上的边数,q就是题目中要求的最多保留边数
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cctype>
#define ll long long
#define maxn 105
using namespace std;
struct ahaha
{
int w,to,next;
}e[maxn<<1];//链式前向星存图
int tot,head[maxn];
inline void add(int u,int v,int w)
{
e[++tot].w=w,e[tot].to=v,e[tot].next=head[u];head[u]=tot;
}
int n,m;
int sz[maxn],f[maxn][maxn];//f[i][j]表示以i为根结点,有j条边的权值最大,转化乘一个01背包问题。
void dfs(int u,int fa)
{
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(v==fa) continue;
dfs(v,u);
sz[u]+=sz[v]+1; //子树边数在加上子节点子树的基础上还要加一,也就是连接子节点子树的那条边
for(int j=min(sz[u],m);j;--j) //由于是01背包,所以要倒序DP
for(int k=min(j-1,sz[v]);k>=0;--k) //这一维正序倒序无所谓,但是把取min放在初始化里可以减少运算次数,算是一个优化的小习惯
f[u][j]=max(f[u][j],f[u][j-k-1]+f[v][k]+e[i].w);
}
}
int main()
{
memset(head,0,sizeof(head));
scanf("%d%d",&n,&m);
for(int i=1;i<n;++i)
{ //前向星存边,要存两边,便于读取
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);add(v,u,w);
}
dfs(1,-1);
printf("%d",f[1][m]);
return 0;
}
串的模式匹配
KMP算法
#include<cstdio>
#include<cstring>
#include<iostream>
const int maxn=1000010;
using namespace std;
char a[maxn],b[maxn];
int f[maxn],j;
int main()
{
scanf("%s%s",a+1,b+1);
int n=strlen(a+1),m=strlen(b+1);
for(int i=2;i<=m;i++)
{
while((b[j+1]!=b[i])&&(j>0))
j=f[j];
if(b[j+1]==b[i]) j++;
f[i]=j;
}
//得到next数组,f为next数组
j=0;
for(int i=1;i<=n;i++)
{
while(j>0&&b[j+1]!=a[i])
j=f[j];
if(b[j+1]==a[i]) j++;
if(j==m)
{
printf("%d\n",i-m+1);
j=f[j];
}
}
for(int i=1;i<=m;i++)
printf("%d ",f[i]);
}
AC自动机
题目描述
每组数据给定N 个模式串和 1个文本串,问有几个模式串在文本串中出现了。
#include<cstdio>
#include<cstring>
int root=0;
const int maxn=1e6+6;
struct
{
int next[26],fail;
int cnt;
}t[maxn];// trie树
int tot;
int queue[maxn];
void init()
{
tot=0;
memset(t,0,sizeof(t));
t[root].fail=-1;
}
void insert(char s[])
{
int now=root;
for(int i=0;s[i]!='\0';i++)
{
if(!t[now].next[s[i]-'a'])
t[now].next[s[i]-'a']=++tot;
now=t[now].next[s[i]-'a'];
}
++t[now].cnt;
}//建立tri树
void build()
{
int head=0,tail=0;
for(int i=0;i<26;i++)
if(t[root].next[i])
{
queue[tail++]=t[root].next[i];
t[t[root].next[i]];
}
while(head!=tail)
{
int fa=queue[head++];
for(int i=0;i<26;i++)
{
if(t[fa].next[i])
{
queue[tail++]=t[fa].next[i];
t[t[fa].next[i]].fail=t[t[fa].fail].next[i];
}
else
t[fa].next[i]=t[t[fa].fail].next[i];
}
}
}//BFS得到fail回溯指针
int find(char s[])
{
int cnt=0,now=root;
for(int i=0;s[i]!='\0';i++)
{
now=t[now].next[s[i]-'a'];
if(~t[now].cnt)
{
for(int j=now;j;j=t[j].fail)
{
if(t[j].cnt==-1)
break;
cnt+=t[j].cnt;
t[j].cnt=-1;
}
}
}
return cnt;
}
char s[maxn];
int main()
{
int n;
scanf("%d",&n);
init();
while(n--)
{
scanf("%s",s);
insert(s);
}
scanf("%s",s);
build();
printf("%d\n",find(s));
return 0;
}
AC自动机加强版
题目描述
有N个由小写字母组成的模式串以及一个文本串T。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串T中出现的次数最多。
输入格式
输入含多组数据。
每组数据的第一行为一个正整数N,表示共有N个模式串
接下去N行,每行一个长度小于等于70的模式串。下一行是文本串T。
输入结束标志为N=0。
输出格式
对于每组数据,第一行输出模式串最多出现的次数,接下去若干行每行输出一个出现次数最多的模式串,按输入顺序排列。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
using namespace std;
struct Tree//字典树
{
int fail;//失配指针
int vis[26];//子节点的位置
int end;//标记以这个节点结尾的单词编号
}AC[100000];//Trie树
int cnt=0;//Trie的指针
struct Result
{
int num;
int pos;
}Ans[100000];//所有单词的出现次数
bool operator <(Result a,Result b)
{
if(a.num!=b.num)
return a.num>b.num;
else
return a.pos<b.pos;
}
string s[100000];
inline void Clean(int x)
{
memset(AC[x].vis,0,sizeof(AC[x].vis));
AC[x].fail=0;
AC[x].end=0;
}
inline void Build(string s,int Num)
{
int l=s.length();
int now=0;//字典树的当前指针
for(int i=0;i<l;++i)//构造Trie树
{
if(AC[now].vis[s[i]-'a']==0)//Trie树没有这个子节点
{
AC[now].vis[s[i]-'a']=++cnt;//构造出来
Clean(cnt);
}
now=AC[now].vis[s[i]-'a'];//向下构造
}
AC[now].end=Num;//标记单词结尾
}
void Get_fail()//构造fail指针
{
queue<int> Q;//队列
for(int i=0;i<26;++i)//第二层的fail指针提前处理一下
{
if(AC[0].vis[i]!=0)
{
AC[AC[0].vis[i]].fail=0;//指向根节点
Q.push(AC[0].vis[i]);//压入队列
}
}
while(!Q.empty())//BFS求fail指针
{
int u=Q.front();
Q.pop();
for(int i=0;i<26;++i)//枚举所有子节点
{
if(AC[u].vis[i]!=0)//存在这个子节点
{
AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i];
//子节点的fail指针指向当前节点的
//fail指针所指向的节点的相同子节点
Q.push(AC[u].vis[i]);//压入队列
}
else//不存在这个子节点
AC[u].vis[i]=AC[AC[u].fail].vis[i];
//当前节点的这个子节点指向当
//前节点fail指针的这个子节点
}
}
}
int AC_Query(string s)//AC自动机匹配
{
int l=s.length();
int now=0,ans=0;
for(int i=0;i<l;++i)
{
now=AC[now].vis[s[i]-'a'];//向下一层
for(int t=now;t;t=AC[t].fail)//循环求解
Ans[AC[t].end].num++;
}
return ans;
}
int main()
{
int n;
while(1)
{
cin>>n;
if(n==0)break;
cnt=0;
Clean(0);
for(int i=1;i<=n;++i)
{
cin>>s[i];
Ans[i].num=0;
Ans[i].pos=i;
Build(s[i],i);
}
AC[0].fail=0;//结束标志
Get_fail();//求出失配指针
cin>>s[0];//文本串
AC_Query(s[0]);
sort(&Ans[1],&Ans[n+1]);
cout<<Ans[1].num<<endl;
cout<<s[Ans[1].pos]<<endl;
for(int i=2;i<=n;++i)
{
if(Ans[i].num==Ans[i-1].num)
cout<<s[Ans[i].pos]<<endl;
else
break;
}
}
return 0;
/*
int n;
string s;
cin>>n;
for(int i=1;i<=n;++i)
{
cin>>s;
Build(s);
}
AC[0].fail=0;//结束标志
Get_fail();//求出失配指针
cin>>s;//文本串
cout<<AC_Query(s)<<endl;
return 0;
普通AC自动机的模板
*/
}
FFT模板
A*B
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long ll;
const double PI = acos(-1.0);
struct comp
{
double r,i;
comp(double rt=0,double it=0)
{
r=rt;
i=it;
}
comp operator +(const comp& b)
{
return comp(r+b.r,i+b.i);
}
comp operator -(const comp &b)
{
return comp(r-b.r,i-b.i);
}
comp operator *(const comp &b)
{
return comp(r*b.r-i*b.i,r*b.i+i*b.r);
}
};
void change(comp y[],int len)//二进制转置--雷德算法
{
int i,j,k;
for(i = 1, j = len/2;i < len-1;i++)
{
if(i < j)swap(y[i],y[j]);
k = len/2;
while( j >= k)
{
j -= k;
k /= 2;
}
if(j < k)j += k;
}
}
void fft(comp y[],int len,int on)
/* on=1 DFT 把一个多项式的系数向量转化为点集表示;
on=-1,IDFT 把一个点集转化成多项式的系数向量*/
{
change(y,len);
for(int h = 2;h <= len;h <<= 1)
{
comp wn(cos(-on*2*PI/h),sin(-on*2*PI/h));
for(int j = 0;j < len;j += h)
{
comp w(1,0);
for(int k = j;k < j+h/2;k++)
{
comp u = y[k];
comp t = w*y[k+h/2];
y[k] = u+t;
y[k+h/2] = u-t;
w = w*wn;
}
}
}
if(on == -1)
for(int i = 0;i < len;i++)
y[i].r /= len;
}
void conv(comp f[],int len)//求f的卷积
{
fft(f,len,1);
for (int i=0; i<len; i++)
f[i]=f[i]*f[i];
fft(f,len,-1);
}
int n,m,len1,len2,len;
char a[50500],b[50500];
comp x[450500],y[450500];
int ans[450500];
void ini(char a[],int l)
{
for(int i=1;i<l;i++)
a[i-1]=a[i];
}
int main()
{
while(~scanf("%s",a))
{
scanf("%s",b);
int l1=strlen(a);
int flag=0;
if(a[0]=='-')
{
flag++;
ini(a,l1);
l1--;
}
for (int i=0; i<l1; i++)
x[l1-i-1]=comp(a[i]-'0',0);
int l2=strlen(b);
for (int i=0; i<l2; i++)
y[l2-i-1]=comp(b[i]-'0',0);
if(b[0]=='-')
{
flag++;
ini(b,l2);
l2--;
}
len=1;
while(len<(l1+l2)*2) len<<=1;
for (int i=l1; i<len; i++) x[i]=comp(0,0);
for (int i=l2; i<len; i++) y[i]=comp(0,0);
fft(x,len,1);
fft(y,len,1);
for (int i=0; i<len; i++)
x[i]=x[i]*y[i];
fft(x,len,-1);
for (int i=0; i<len; i++)
ans[i]=(int)(x[i].r+0.5);
for (int i=0; i<len; i++)
if (ans[i]>9)
{
ans[i+1]+=ans[i]/10;
ans[i]%=10;
}
bool ok=false;
if(flag%2!=0) printf("-");
for (int i=len-1; i>=0; i--)
{
if (ok) printf("%d",ans[i]);
else if (ans[i])
{
ok=true;
printf("%d",ans[i]);
}
}
if (!ok) puts("0");
else puts("");
}
}
FFT字符串匹配
题目描述
给出两段石头剪刀布的顺序S和T,其中T要短一些,现在让你把T往S的某个位置上靠,使得靠好了以后,T能赢S的子段的次数最大。
题解
这道题很典型的FFT啦,首先我们把TT序列换成它能赢的序列T′,也就是TT序列中的R、S、P对应的换成S、P、R形成T′。
这样的话,我们只需要在S中找一段匹配程度最大的就可以了,这的最大的匹配程度就是答案。
为了匹配,我们把T′倒换过来,记为rT′,我们想象一下做卷积的过程,
发现新的卷积序列中的第k个位置的值等于S[k−lenT,k−1]与T′序列对应位置乘积之和。
为了使用卷积解决这个问题,我们把问题拆成3部分,即单独考虑P、S、R时候,最大匹配程度,最后将相同位置的匹配程度加起来就可以了。
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
double pi = acos(-1.0);
struct complex{
double re,im;
complex(double r = 0.0,double i = 0.0):re(r),im(i){};
complex operator+(complex com){
return complex(re+com.re,im+com.im);
}
complex operator-(complex com){
return complex(re-com.re,im-com.im);
}
complex operator*(complex com){
return complex(re*com.re-im*com.im,re*com.im+im*com.re);
}
};
complex wn,wntmp;
void rader(complex arr[],int n){
int num = n-1;
for(int i = 0;i < n;++i){
int tn = n>>1;
while(num && num >= tn) num ^= tn,tn >>= 1;
num |= tn;
if(num > i) swap(arr[i],arr[num]);
}
}
void FFT(complex cs[],int n,int f){
rader(cs,n);
for(int s = 1;s < n;s <<= 1){
wn = complex(cos(f*2*pi/(s*2)),sin(f*2*pi/(s*2)));
for(int offset = 0;offset < n;offset += s<<1){
wntmp = complex(1.0,0.0);
for(int i = 0;i < s;++i){
complex u = cs[offset+i],v = cs[offset+i+s]*wntmp;
cs[offset+i] = u + v;
cs[offset+i+s] = u - v;
wntmp = wntmp * wn;
}
}
}
if(f == -1)
for(int i = 0;i < n;++i)
cs[i].re /= n;
}
int n,m;
const int maxn = 1e5+7;
char S[maxn],T[maxn];
int ans[maxn*4];
complex csA[maxn*4],csB[maxn*4];
#define pr(x) cout<<#x<<":"<<x<<endl
void solve(char c){
memset(csA,0,sizeof(csA));
memset(csB,0,sizeof(csB));
for(int i = 0;i < n;++i) csA[i] = complex(S[i]==c?1.0:0);
for(int i = 0;i < m;++i) csB[i] = complex(T[i]==c?1.0:0);
int len = 1;
while(len < n) len<<=1;
len <<= 1;
FFT(csA,len,1);
FFT(csB,len,1);
for(int i = 0;i < len;++i) csA[i] = csA[i]*csB[i];
FFT(csA,len,-1);
for(int i = m-1;i < len;++i) {
ans[i] += int(csA[i].re+0.5);
};
}
char big(char c){
if(c == 'R') return 'S';
if(c == 'S') return 'P';
if(c == 'P') return 'R';
}
int main(){
cin>>n>>m>>S>>T;
for(int i = 0;i < m/2;++i) swap(T[i],T[m-1-i]);
for(int i = 0;i < m;++i) T[i] = big(T[i]);
solve('P');
solve('S');
solve('R');
int mx = 0;
for(int i = 0;i < n+m+1;++i) mx = max(mx,ans[i]);
cout<<mx<<endl;
return 0;
}
几何算法
查找平面最近点对
题目描述
定平面上n个点,找其中的一对点,使得在n个点组成的所有点对中,该点对间的距离最小。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
struct point
{
double x,y;
}p[100005];
int a[100005];
int cmpx(const point &a,const point &b)
{
return a.x < b.x;
}
int cmpy(const int &a,const int &b)
{
return p[a].y < p[b].y;
}
double dis(int a,int b)
{
return sqrt((p[a].x - p[b].x) * (p[a].x - p[b].x) + (p[a].y - p[b].y) * (p[a].y - p[b].y));
}
double min(double x,double y)
{
return x < y ? x : y;
}
double cloest(int left, int right)
{
if(left == right)
return 1000000;
if(left + 1 == right)
return dis(left,right);
int mid = (left + right) >> 1;
double d1 = cloest(left,mid); //递归求左部分最近点距离
double d2 = cloest(mid+1,right); //右部分
double d = min(d1,d2);
int i,j,k = 0;
for( i = left ; i <= right; i++ )
{
if(fabs(p[mid].x - p[i].x) < d) //记录和mid位置点小于d的点的位置
a[k++] = i; //注意这里记录的是位置号
}
sort(a,a+k,cmpy); //按y坐标排序
for( i = 0; i < k - 1; i++ )
{
for( j = i+1; j < i + 7 && j < k; j++ )
{
if(p[a[j]].y - p[a[i]].y >= d)
break;
d = min(d,dis(a[i],a[j]));
}
}
return d;
}
int main()
{
int i,n;
while(scanf("%d",&n) != 0)
{
if(!n)
break;
for( i = 0; i < n; i++ )
{
scanf("%lf %lf",&p[i].x,&p[i].y);
}
sort(p,p+n,cmpx); //按x坐标排序
printf("%.2f\n",cloest(0,n-1) / 2);
}
return 0;
}
圆相关
Point waixin(Point a,Point b,Point c)
{
double a1 = b.x - a.x, b1 = b.y - a.y, c1 = (a1*a1 + b1*b1)/2;
double a2 = c.x - a.x, b2 = c.y - a.y, c2 = (a2*a2 + b2*b2)/2;
double d = a1*b2 - a2*b1;
return Point(a.x + (c1*b2 - c2*b1)/d, a.y + (a1*c2 -a2*c1)/d);
}//求圆心
double Area_of_overlap(Point c1,double r1,Point c2,double r2)
{
double d = dist(c1,c2);
if(r1 + r2 < d + eps)return 0;
if(d < fabs(r1 - r2) + eps)
{
double r = min(r1,r2);
return PI*r*r;
}
double x = (d*d + r1*r1 - r2*r2)/(2*d);
double t1 = acos(x / r1);
double t2 = acos((d - x)/r2);
return r1*r1*t1 + r2*r2*t2 - d*r1*sin(t1);
} //求两圆相交部分
四点求交点
#include<iostream>
#include<cmath>
#include<vector>
#include<cstdio>
using namespace std;
typedef struct node
{
double x, y;
}NODE;
NODE A1, A2, B1, B2;
inline NODE Vector(NODE a, NODE b);
double dis2(NODE a, NODE b);
double cross(NODE A, NODE B, NODE P);
double dot(NODE A, NODE B, NODE P);
int dir(NODE A, NODE B, NODE P);
double disLine(NODE A, NODE B, NODE P);
int operator ==(const NODE a, const NODE b) {
if(a.x == b.x && a.y == b.y) return 1;
return 0;
}
int main()
{
while(scanf("%lf%lf%lf%lf%lf%lf%lf%lf",&A1.x,&A1.y,&A2.x,&A2.y,&B1.x,&B1.y,&B2.x,&B2.y) != EOF)
{
if (dir(A1, A2, B1) * dir(A1, A2, B2) <= 0 && dir(B1, B2, A1) * dir(B1, B2, A2) <= 0)
{//判断有无交点
double t = disLine(A1, A2, B1) / (disLine(A1, A2, B1) + disLine(A1, A2, B2));
NODE B1B2 = Vector(B1, B2);
NODE inter = { B1.x + B1B2.x * t, B1.y + B1B2.y * t };
if(!isnan(inter.x) || !isnan(inter.y))
{
printf("%lf %lf\n",inter.x, inter.y);
}
else
{
if(dis2(A1,A2) < dis2(B1,B2))
{
swap(A1,B1);
swap(A2,B2);
}
if(dis2(A1,B1) == 0) // A1 是交点
{
if(dis2(A1,B2) + dis2(A2,B2) > dis2(A1,A2))
{
printf("%lf %lf\n",B1.x,B1.y);
}
else printf("none\n");
}
else if(dis2(A1,B2) == 0)
{
if(dis2(A1,B1) + dis2(A2,B1) > dis2(A1,A2))
{
printf("%lf %lf\n",B2.x,B2.y);
}
else printf("none\n");
}
else if(dis2(A1,A2) == dis2(A1,B1))
{
if(dis2(A1,B2) + dis2(A2,B2) > dis2(A1,A2))
{
printf("%lf %lf\n",B1.x,B1.y);
}
else printf("none\n");
}
else if(dis2(A1,A2) == dis2(A1,B2))
{
if(dis2(A1,B1) + dis2(A2,B1) > dis2(A1,A2))
{
printf("%lf %lf\n",B2.x,B2.y);
}
else printf("none\n");
}
else
{
printf("none\n");
}
}
}
else
{
printf("none\n");
}
}
return 0;
}
inline NODE Vector(NODE a, NODE b) //求向量
{
return{ b.x - a.x, b.y - a.y };
}
double dis2(NODE a, NODE b) //两点间的距离的平方
{
return (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y);
}
double cross(NODE A, NODE B, NODE P) //向量的外积(叉积) , 以 A 为中间点
{
NODE AB = Vector(A,B);
NODE AP = Vector(A,P);
return AB.x * AP.y - AB.y * AP.x;
}
double dot(NODE A, NODE B, NODE P) //向量的内积
{
NODE AB = Vector(A,B);
NODE AP = Vector(A,P);
return AB.x * AP.x + AB.y*AP.y;
}
int dir(NODE A, NODE B, NODE P) //点与线段方位判定
{
if (cross(A, B, P) > 0) return -1;
else if (cross(A, B, P) < 0) return 1;
else if (dot(A, B, P) < 0) return -2;
else if (dot(A, B, P) >= 0)
{
if (dis2(A, B) < dis2(A, P)) return 2;
else return 0;
}
}
double disLine(NODE A, NODE B, NODE P) //点P到直线AB的距离
{
return fabs(cross(A, B, P)) / sqrt(dis2(A, B));
}
最小圆覆盖
#include <stdio.h>
#include <math.h>
const int maxn = 1005;
//const double eps = 1e-6;
struct TPoint
{
double x, y;
TPoint operator-(TPoint &a)
{
TPoint p1;
p1.x = x - a.x;
p1.y = y - a.y;
return p1;
}
};
struct TCircle
{
double r;
TPoint centre;
};
struct TTriangle
{
TPoint t[3];
};
TCircle c;
TPoint a[maxn];
double distance(TPoint p1, TPoint p2)
{
TPoint p3;
p3.x = p2.x - p1.x;
p3.y = p2.y - p1.y;
return sqrt(p3.x * p3.x + p3.y * p3.y);
}
double triangleArea(TTriangle t)
{
TPoint p1, p2;
p1 = t.t[1] - t.t[0];
p2 = t.t[2] - t.t[0];
return fabs(p1.x * p2.y - p1.y * p2.x) / 2;
}
TCircle circumcircleOfTriangle(TTriangle t)
{
//三角形的外接圆
TCircle tmp;
double a, b, c, c1, c2;
double xA, yA, xB, yB, xC, yC;
a = distance(t.t[0], t.t[1]);
b = distance(t.t[1], t.t[2]);
c = distance(t.t[2], t.t[0]);
//根据S = a * b * c / R / 4;求半径R
tmp.r = a * b * c / triangleArea(t) / 4;
xA = t.t[0].x; yA = t.t[0].y;
xB = t.t[1].x; yB = t.t[1].y;
xC = t.t[2].x; yC = t.t[2].y;
c1 = (xA * xA + yA * yA - xB * xB - yB * yB) / 2;
c2 = (xA * xA + yA * yA - xC * xC - yC * yC) / 2;
tmp.centre.x = (c1 * (yA - yC) - c2 * (yA - yB)) /
((xA - xB) * (yA - yC) - (xA - xC) * (yA - yB));
tmp.centre.y = (c1 * (xA - xC) - c2 * (xA - xB)) /
((yA - yB) * (xA - xC) - (yA - yC) * (xA - xB));
return tmp;
}
TCircle MinCircle2(int tce, TTriangle ce)
{
TCircle tmp;
if(tce == 0) tmp.r = -2;
else if(tce == 1)
{
tmp.centre = ce.t[0];
tmp.r = 0;
}
else if(tce == 2)
{
tmp.r = distance(ce.t[0], ce.t[1]) / 2;
tmp.centre.x = (ce.t[0].x + ce.t[1].x) / 2;
tmp.centre.y = (ce.t[0].y + ce.t[1].y) / 2;
}
else if(tce == 3) tmp = circumcircleOfTriangle(ce);
return tmp;
}
void MinCircle(int t, int tce, TTriangle ce)
{
int i, j;
TPoint tmp;
c = MinCircle2(tce, ce);
if(tce == 3) return;
for(i = 1;i <= t;i++)
{
if(distance(a[i], c.centre) > c.r)
{
ce.t[tce] = a[i];
MinCircle(i - 1, tce + 1, ce);
tmp = a[i];
for(j = i;j >= 2;j--)
{
a[j] = a[j - 1];
}
a[1] = tmp;
}
}
}
void run(int n)
{
TTriangle ce;
int i;
MinCircle(n, 0, ce);
printf("%.2lf %.2lf %.2lf\n", c.centre.x, c.centre.y, c.r);
}
int main()
{
freopen("circle.in", "r", stdin);
freopen("out.txt", "w", stdout);
int n;
while(scanf("%d", &n) != EOF && n)
{
for(int i = 1;i <= n;i++)
scanf("%lf%lf", &a[i].x, &a[i].y);
run(n);
}
return 0;
}
求多边形重心
题目描述
有一个密度均匀的平面N多边形(3 <= N <= 1000000),可能凹也可能凸,但没有边相交叉,另外已知N个有序(顺时针或逆时针)顶点的坐标值,第j个顶点坐标为(Xi , Yi ),且满足 (|Xi|, |Yi| <= 20000),求这个平面多边形的重心。
#include <stdio.h>
#include <math.h>
typedef struct TPoint
{
double x;
double y;
}TPoint;
double triangleArea(TPoint p0, TPoint p1, TPoint p2)
{
double k = p0.x * p1.y + p1.x * p2.y
+ p2.x * p0.y - p1.x * p0.y
- p2.x * p1.y - p0.x * p2.y;
return k / 2;
}
int main()
{
int i, n, test;
TPoint p0, p1, p2, center;
double area, sumarea, sumx, sumy;
scanf("%d", &test);
while(test--){
scanf("%d", &n);
scanf("%lf%lf", &p0.x, &p0.y);
scanf("%lf%lf", &p1.x, &p1.y);
sumx = 0;
sumy = 0;
sumarea = 0;
for(i = 2;i < n;i++){
scanf("%lf%lf", &p2.x, &p2.y);
center.x = p0.x + p1.x + p2.x;
center.y = p0.y + p1.y + p2.y;
area = triangleArea(p0, p1, p2);
sumarea += area;
sumx += center.x * area;
sumy += center.y * area;
p1 = p2;
}
printf("%.2lf %.2lf\n", sumx / sumarea / 3, sumy / sumarea / 3);
}
return 0;
}
存不存在一个平面把两堆点分开
#include <stdio.h>
struct point
{
double x, y, z;
}pa[201], pb[201];
int main()
{
int n, m, i;
while (scanf("%d", &n), n != -1)
{
for (i = 0; i < n; i++)
scanf("%lf%lf%lf", &pa[i].x, &pa[i].y, &pa[i].z);
scanf("%d", &m);
for (i = 0; i < m; i++)
scanf("%lf%lf%lf", &pb[i].x, &pb[i].y, &pb[i].z);
int cnt = 0, finish = 0;
double a = 0, b = 0, c = 0, d = 0;
while (cnt < 100000 && !finish)
{
finish = 1;
for (i = 0; i < n; i++)
if (a * pa[i].x + b * pa[i].y + c * pa[i].z + d > 0)
{
a -= pa[i].x;
b -= pa[i].y;
c -= pa[i].z;
d -= 3;
finish = 0;
}
for (i = 0; i < m; i++)
if (a * pb[i].x + b * pb[i].y + c * pb[i].z + d <= 0)
{
a += pb[i].x;
b += pb[i].y;
c += pb[i].z;
d += 3;
finish = 0;
}
cnt++;
}
printf("%lf %lf %lf %lf\n", a, b, c, d);
}
return 0;
}
高精
a*b
#include<iostream>
#include<cstring>
using namespace std;
char a1[50001],b1[50001];
int a[50001],b[50001],i,x,len,j,c[50001];
int main ()
{
cin >>a1 >>b1;//读入两个数
a[0]=strlen(a1);b[0]=strlen(b1);//计算长度
for (i=1;i<=a[0];++i)a[i]=a1[a[0]-i]-'0';//将字符串转换成数字
for (i=1;i<=b[0];++i)b[i]=b1[b[0]-i]-'0';
for (i=1;i<=a[0];++i)for (j=1;j<=b[0];++j)c[i+j-1]+=a[i]*b[j];//按乘法
len=a[0]+b[0]; //原理进行高精乘
for (i=1;i<len;++i)if (c[i]>9){c[i+1]+=c[i]/10;c[i]%=10;}//进位
while (c[len]==0&&len>1)len--;//判断位数
for (i=len;i>=1;--i)cout <<c[i];//输出
return 0;
}
a/b
#include<string>
#include<iostream>
using namespace std;
string a,c;
int b,i,d;
int main()
{
cin>>a>>b; //神奇的读入
for (;i<a.length();i++)a[i]-=48; //字符串转数字
for (i=0;i<a.length();i++)
c.push_back((d*10+a[i])/b+48),d=(d*10+a[i])%b; //模拟竖式
for (i=0;c[0]==48;i++)c.erase(c.begin(),c.begin()+1); //去0
cout<<c; //华丽的输出
return 0; //完美的结束
}
a+b
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
string a,b,c;
int main(){
ios::sync_with_stdio(false);//取消同步,输入输出优化
cin>>a>>b;
int l=a.size(),q=b.size(),h,k=0;
if(l>q) c=a,a=b,b=c,h=l,l=q,q=h;//保证B比A长
for(int i=1;i<=q;++i){
if(i<=l) h=a[l-i]-48;//减48是因为char比int值大48
else h=0;
c[i]=b[q-i]+h-48+k;
if(c[i]>9){c[i]-=10;k=1;}
else k=0;
}
if(k) cout<<1;//防止和值比加数多一位
for(int i=q;i>0;--i) cout<<(short)c[i];//不加(short)会输出一些奇奇怪怪的东西
return 0;
}
stl
查找adjacent_find()
sort(a,a+n);
int* flag=adjacent_find(a,a+n);//adjacent_find返回第一次找到连续相同的数的第几个;
去重unique
n = unique(a, a + n) - a; //关键的一句f,返回新的最后一个地址,减去a,得到新地址。
第K顺序量
调用方式为nth_element(a+l,a+k,a+r+1),意思就是在a数组中的区间[l,r]之间第k小的值放到第k个位置,比这个第k小值小的放到它的前面。同理,比这个第k小值大的放到它后面
题目
现有n个正整数,n≤10000,要求出这n个正整数中的第k个最小整数(相同大小的整数只计算一次),k≤1000,正整数均小于30000。
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
int main(){
int n,a[10010],k;
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
n=unique(a+1,a+n+1)-a-1;
nth_element(a+1,a+k,a+n+1);
if(k>n) printf("NO RESULT");
else
printf("%d\n", a[k]);
return 0;
}
dp0背包的加强
题目描述
卡门预先知道了每个垃圾扔下的时间t(0<t≤1000),以及每个垃圾堆放的高度h(1≤h≤25)和吃进该垃圾能维持生命的时间f(1≤f≤30),要求出卡门最早能逃出井外的时间,假设卡门当前体内有足够持续10小时的能量,如果卡门10小时内没有进食卡门就将饿死。
输出
如果卡门可以爬出陷阱,输出一个整表示最早什么时候可以爬出;否则输出卡门最长可以存活多长时间。
题解
这个题非常的像01背包,但是又有不同之处,因为01背包中的“不装”就不对状态做修改,这里不装则是将垃圾堆起来。
把垃圾的高度看成物重,能增加的生命的长短看成价值,然后把井的高度看成包的大小,要求必须把包填满(或爆)能取得的最小价值。
直接搞一个背包:
设dp[i][j]表示前i个垃圾(注意一定要先按垃圾出现时间排序好),到达高度j时所拥有的最长的生命时间。
dp[i+1][j+tr[i+1].h]=dp[i][j]-tr[i+1].t+tr[i].t;(填,不吃)
dp[i+1][j]=max(dp[i+1][j],dp[i][j]-tr[i+1].t+tr[i].t+tr[i+1].f);(吃,不填)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int g,d;
struct zy
{
int t,h,f;
}tr[110];
bool cmp(zy a,zy b)
{
return a.t<b.t;
}
int dp[110][1000];
int main()
{
scanf("%d%d",&d,&g);
memset(dp,-1,sizeof(dp));
for(int i=1;i<=g;i++)
scanf("%d%d%d",&tr[i].t,&tr[i].f,&tr[i].h);
sort(tr+1,tr+1+g,cmp);
dp[0][0]=10;
tr[0].f=0;
tr[0].h=0;
tr[0].t=0;
//存储在扔进去i个垃圾,高度为j时的最大生命值
for(int i=0;i<g;i++)
{
for(int j=0;j<=d;j++)
{
if(dp[i][j]<0)//没有奶牛的状态
continue;
if(j+tr[i+1].h>=d&&dp[i][j]>=tr[i+1].t-tr[i].t)
{
printf("%d\n",tr[i+1].t);
return 0;
}//转移这个状态时,既满足下一个垃圾可以直接跳出去,又满足奶牛的能量能坚持到下一个垃圾
if(dp[i][j]-tr[i+1].t+tr[i].t>=0)
dp[i+1][j+tr[i+1].h]=dp[i][j]-tr[i+1].t+tr[i].t;
if(dp[i][j]-tr[i+1].t+tr[i].t>=0)
dp[i+1][j]=max(dp[i+1][j],dp[i][j]-tr[i+1].t+tr[i].t+tr[i+1].f);
//这里max因为dp[i+1][j]可能本来有值
}
}
//如果进行到当前状态,说明没能跳出去
//全吃掉 重新模拟一遍
//当奶牛不能坚持时,要将现在的能量用完
int m=10,sum=0;
for(int i=1;i<=g;i++)
{
if(tr[i].t-tr[i-1].t>m)
{
printf("%d\n",sum+m);
return 0;
}
sum+=tr[i].t-tr[i-1].t;
m-=tr[i].t-tr[i-1].t;
m+=tr[i].f;
}
printf("%d\n",sum+m);
}