算法模板-树形DP+字符串匹配+FFT+计算几何+高精

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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值