V4yneのACM模板----
关于__int128的使用:
typedef __int128 ll;
inline __int128 read()//__int128的读入
{
__int128 x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
inline void write(__int128 x)//__int128的输出
{
if(x<0)
{
putchar('-');
x=-x;
}
if(x>9)
write(x/10);
putchar(x%10+'0');
}
关闭cin和cout的同步以及endl的快速输出优化:
#define endl '\n'
ios::sync_with_stdio(false);//写在int main主函数的第一行
二分板子:
使用二分时要注意题目要求的东西是否满足单调性,满足二分可求性。
int fin=xxx值;//(看求什么决定初始值赋什么,一般为0或n)
int l=0;int r=n;
while(l<=r)
{
int mid=(l+r+1)>>1;
if(check(mid))
{
l=xxx或者r=xxx;
fin=max(mid,fin);//或者fin=min(mid,fin);
}
else l=xxx或者r=xxx;
//根据mid是否符合来具体确定l和r变成mid+1或者是mid-1,另一个不变。
}
//l和r变化为mid-1或mid+1时,条件严格写成while(l<=r)
rand()随机函数的用法:
产生[m,n]范围内的随机数num,可用:
int num=rand()%(n-m+1)+m;
离散化
//离散化的总结
/*
当很多数字很大需要离散化时,先将它们存进一个book数组中,
然后对该数组排序,排序完后unique去重一下,然后每一个元素的
离散化后的值是lower_bound出的下标的值。
lower_bound返回的是数组中的下标。
unique函数减去去重部分的初始位置就是这个去重后的区间的元素个数。无脑减初始位置就行。
*/
book[++tot]=aa[i];
sort(book,book+tot+1);
int res=unique(book,book+tot+1)-book;
int a=lower_bound(book,book+res,aa[i])-book;
吉姆拉尔森计算星期公式:
对于y年m月d日。
对于每一个日期,输入y,m,d,则可以由吉姆拉尔森公式计算出那一天是星期几。
吉姆拉尔森公式:w=(d+2m+3(m+1)/5+y+y/4-y/100+y/400)%7+1;
w对应的数字为1 2 3 4 5 6 7,为周一到周日。
w=(d+2*m+3*(m+1)/5+y+y/4-y/100+y/400)%7+1;
二项式展开中Cxy的求法:
C[i][j]就是Cij。
void init()
{
c[0][0]=1;
for(int i=1;i<=100;++i)
{
c[i][0]=c[i][i]=1;
for(int j=1;j<i;++j)
c[i][j]=(c[i-1][j-1]%mod+c[i-1][j]%mod)%mod;
}
}
前向星模板
struct node
{
int to,next;
}edge[2*maxn];//存储的是边,双向边之类,一般开边数*2
int e,head[maxn];
void edge_init()
{
memset(head,0,sizeof(head));
e=0;
}
void add_edge(int u,int v)
{
edge[++e].to=v;
edge[e].next=head[u];
head[u]=e;
}
存边权值的前向星:
struct node
{
int to,next,val;
}edge[2*maxn];//存储的是边,双向边之类,一般开边数*2
int e,head[maxn];
void edge_init()
{
memset(head,0,sizeof(head));
e=0;
}
void add_edge(int u,int v)
{
edge[++e].to=v;
edge[e].next=head[u];
edge[e].val=val;
head[u]=e;
}
前向星边的遍历
for(int i=head[x];i;i=edge[i].next)//对起点是x的所有边的遍历
{
int y=edge[i].to; //y是该边的终点
}
树状数组:
NOTE:
1.需要注意的地方是,树状数组的add函数,对pos为0的位置加入值时,会tle。
2.树状数组的tre数组大小开小了的时候也会报tle。
int N; //N是数组的最大下标
int tre[maxn];
int lowbit(int x)
{
return x&(-x);
}
void add(int pos,int val)//pos位置加上val的修改
{
while(pos<=N)
{
tre[pos]+=val;
pos+=lowbit(pos);
}
}
ll ask(int pos)
{
ll ans=0;
while(pos)
{
ans+=tre[pos];
pos-=lowbit(pos);
}
return ans;
}
快速幂:
ll cheng(ll a,ll b,ll p)
{
ll ans=0;
while(b)
{
if(b&1) ans=(ans+a)%p;
a+=a;a%=p;
b=b>>1;
}
return ans;
}
ll qk(ll a,ll b,ll p)
{
ll ans=1%p;
while(b)
{
if(b&1) ans=cheng(ans,a,p);
a=cheng(a,a,p);
b=b>>1;
}
return ans;
}
//单调队列数组模拟写法
int head,tail;
head=1;
int mi[maxn];//最小值的单调队列
int mx[maxn];//最大值的单调队列
for(遍历单调队列维护的目标数组)
{
while(head<=tail&&max(i-l+1,1)>mi[head]) head++;//l是所维护的区间长度
while(head<=tail&&arr[mi[tail]]>=arr[i]) tail--;
//while(head<=tail&&arr[mx[tail]]<=arr[i]) tail--;
mi[++tail]=i;
//mx[++tail]=i;
//当前的元素一定会插入到单调队列中的。
//目前的最值就是head的值
//数组模拟的单调队列维护的是最值在arr数组中的下标,而不是具体的一个值
}
字符串hash模板:
字符串hash:
字符串hash的复杂度是O(n),n是被hash的字符串的长度。
字符串hash的时间复杂度极其优秀。
查询是O(1)的复杂度。
字符串hash类似于一种加密方式,可以将字符串映射成一个数字,每种字符串可以对应到唯一的一个数字,所以查找时直接比对加密后的数字是否相等就可以判断这两个字符串是否相等了。
模板:
typedef unsigned long long ull;
ull base=13131;
ull rec[maxn];
char s[maxn];//下标从1开始
int len=strlen(s+1);
ull p[maxn];
p[0]=1;
for(int i=1;i<=len;i++)
{
p[i]=p[i-1]*base;
rec[i]=rec[i-1]*base+s[i]-'a';
}
//这样就将字符串s的开头位置到每个位置的字符串都映射成了一个数字。rec数组存的是字符串s的所有前缀字符串的hash值。
//p数组存的是base的幂次方值,下标为i的存的是base的i次方的值。
//**字符串hash的另一种用法:字符串s的每一个子串的hash值都可以求出来,设这个子串的开头为l,结尾为r,那么其hash值为rec[r]-rec[l-1]*p[r-l+1];
kmp模板
洛谷P3375
kmp的复杂度就是两个字符串的长度。
//kmp的用法—循环节:
求字符串的最小循环节长度,cir=len-nxt [len],如果恰好能被len整除,则说明整个字符串就是一个循环字符串,如果不能被整除,需要补上(cir - len)%cir长度的字符串来使其变成一个循环字符串。
const int maxn=1000000+50;
char s1[maxn];
char s2[maxn];
int nxt[maxn];
vector<int> fin;//fir记载的是每一次出现的位置
int fir,ans;//fir是第一次出现的位置,ans是出现的总次数。
void get_next(char *str)//求str的nxt数组,0~len-1的字符的nxt值存在1~len中
{
int i=0;int j=-1;nxt[0]=-1;
int len=strlen(str);
while(i<len)
{
if(j==-1||str[i]==str[j]) nxt[++i]=++j;
else j=nxt[j];
}
}
void kmp(char *s1,char *s2)//在s1中找s2
{
fir=-1;ans=0;fin.clear();
get_next(s2);//找s2要对s2求nxt数组
int len=strlen(s2);
int i,j;i=j=0;
int len1=strlen(s1);int len2=strlen(s2);
while(i<len1)
{
if(j==-1||s1[i]==s2[j]) i++,j++;
else j=nxt[j];
if(j>=len2)//在s1中找到了s2了。
{
if(fir==-1) fir=i-len2+1;//fir是出现的位置+1,最小是1而不是0,根据题目询问输出。。
ans++;
fin.push_back(i-len2+1);
j=nxt[j];
//用过的元素还能用时j这么变化,否则j=i+1;
}
}
}
//用法kmp(s1,s2);
//s1和s2读入时从0读入。
扩展kmp模板:
洛谷P5410
扩展kmp求的是字符串b与a的每一个后缀的最长公共前缀长度。
存在extend数组里面。
#include
using namespace std;
typedef long long ll;
const int N=1e6+50;
int q,nxt[N],extend[N];
char s[N];
char t[N];
int cas;
void getnxt()
{
// nxt[0]=t.size();//nxt[0]一定是T的长度
int len=strlen(t);
nxt[0]=len;
int now=0;
while(t[now]==t[1+now]&&now+1<len)now++;//这就是从1开始暴力
nxt[1]=now;
int p0=1;
for(int i=2;i<len;i++)
{
if(i+nxt[i-p0]<nxt[p0]+p0)nxt[i]=nxt[i-p0];//第一种情况
else
{
//第二种情况
int now=nxt[p0]+p0-i;
now=max(now,0);//这里是为了防止i>p的情况
while(t[now]==t[i+now]&&i+now<len)now++;//暴力
nxt[i]=now;
p0=i;//更新p0
}
}
}
void exkmp()
{
getnxt();
int now=0;
int lens=strlen(s);
int lent=strlen(t);
for(int i=0;i<=lens;i++) extend[i]=0;
while(s[now]==t[now]&&now<min(lens,lent))now++;//暴力
extend[0]=now;
int p0=0;
for(int i=1;i<lens;i++)
{
if(i+nxt[i-p0]<extend[p0]+p0)extend[i]=nxt[i-p0];//第一种情况
else
{
//第二种情况
int now=extend[p0]+p0-i;
now=max(now,0);//这里是为了防止i>p的情况
while(t[now]==s[i+now]&&now<lent&&now+i<lens) now++;//暴力
extend[i]=now;
p0=i;//更新p0
}
}
}
int main()
{
scanf("%s",s);
scanf("%s",t);
exkmp();
int lent=strlen(t);
int lens=strlen(s);
for(int i=0;i<lent;i++)
{
printf("%d ",nxt[i]);
}
printf("\n");
for(int i=0;i<lens;i++)//extend求出来是s的每一个后缀与t匹配的最长公共前缀。
{
printf("%d ",extend[i]);
}
return 0;
}
manacher马拉车算法:
#include
using namespace std;
const int maxn=3e7;
char s[maxn];
char a[maxn];
int hw[maxn];//数组最远扩展的距离。
int cnt;
void change()
{
s[1]='~';s[2]='#';
cnt=2;
int len=strlen(a+1);
for(int i=1;i<=len;i++)
{
s[++cnt]=a[i];
s[++cnt]='#';
}
s[++cnt]='!';
}
void manacher()
{
change();
int maxright=0;int mid=0;
for(int i=2;i<=cnt-1;i++)
{
if(i<=maxright)
{
hw[i]=min(hw[mid*2-i],maxright-i+1);
}
else hw[i]=1;
while(s[i+hw[i]]==s[i-hw[i]])
{
++hw[i];
}
if(hw[i]+i-1>=maxright)
{
maxright=i+hw[i]-1;
mid=i;
}
}
}
int main()
{
scanf("%s",a+1);
manacher();
int mx=0;
for(int i=2;i<=cnt-1;i++)
{
mx=max(mx,hw[i]);
}
printf("%d\n",mx-1);
return 0;
}
//后缀数组sa,rank,height的求法
char s[maxn]; //s是从0开始的,到n-1
int rnk[maxn],sa[maxn],height[maxn],tmp[maxn],cnt[maxn];
//sa和height是1到n,rank是从0到n-1
//数组得开字符串长度的四倍
void get_sa(int n,int m)//n是字符串长度,m是最大字符大小
{
n++;
int i,j,k;
for(i=0;i<n*2+5;i++) rnk[i]=sa[i]=height[i]=tmp[i]=0;
for(i=0;i<m;i++) cnt[i]=0;
for(i=0;i<n;i++) cnt[rnk[i]=s[i]]++;
for(i=1;i<m;i++) cnt[i]+=cnt[i-1];
for(i=0;i<n;i++) sa[--cnt[rnk[i]]]=i;
for(k=1;k<=n;k<<=1)
{
for(i=0;i<n;i++)
{
int j=sa[i]-k;
if(j<0) j+=n;
tmp[cnt[rnk[j]]++]=j;
}
sa[tmp[cnt[0]=0]]=j=0;
for(i=1;i<n;i++)
{
if(rnk[tmp[i]]!=rnk[tmp[i-1]]||rnk[tmp[i]+k]!=rnk[tmp[i-1]+k])
cnt[++j]=i;
sa[tmp[i]]=j;
}
memcpy(rnk,sa,n*sizeof(int));
memcpy(sa,tmp,n*sizeof(int));
if(j>=n-1) break;
}
for(j=rnk[height[i=k=0]=0];i<n-1;i++,k++)
{
while(~k&&s[i]!=s[sa[j-1]+k]) height[j]=k--,j=rnk[sa[j]+1];
}
}
void st(int n)
{
for(int i=2;i<=n;i++)
Log[i]=Log[i>>1]+1;
for(int i=1;i<=n;i++)
fmi[0][i]=height[i];//st函数中的数组的导入位置
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
{
fmi[j][i]=min(fmi[j-1][i],fmi[j-1][i+(1<<j-1)]);
}
}
int ask_min(int x,int y)
{
int k=Log[y-x+1];
return min(fmi[k][x],fmi[k][y-(1<<k)+1]);
}
int lcp(int x,int y)//询问字符串中第x位和第y位的后缀的最长公共前缀
{
int a=rnk[x];
int b=rnk[y];
if(a>b) swap(a,b);
a++;
int zz=ask_min(a,b);
return zz;
}
后缀自动机模板
//后缀自动机的插入是一个一个地插入字符
//字符串都是从0开始
//maxn要开的比字符串长度大几倍
int mx[maxn]; //为work准备的
int n;
struct SAM//root是1,next不存在是0。
{
int next[maxn][30],fa[maxn],len[maxn];//每个节点的fa节点要么是它的后缀要么就是根节点。
int c[maxn],id[maxn];
ll size[maxn];//size数组是每一个endpos在字符串中出现的次数。
int root,tot,last;
int val(int x)//每个endpos的存储的不同字符串种类数
{
return len[x]-len[fa[x]];
}
int newnode(int l)//创建一个新的状态节点 //l是新节点的长度
{
fa[tot]=0;
for(int i=0;i<30;++i) next[tot][i]=0;
len[tot++]=l; return tot-1;
}
void init()//后缀自动机的初始化
{
memset(len,0,sizeof(len));
memset(fa,0,sizeof(fa));
memset(c,0,sizeof(c));
memset(id,0,sizeof(id));
memset(size,0,sizeof(size));
tot=1;
last=root=newnode(0);
}
void extend(int x)//加入一个新的字符 //sam.extend(s[i]-'a');
{
int p=last; int cur=newnode(len[p]+1);size[cur]=1;//cur是新节点
while(p!=0&&next[p][x]==0)//把后缀全都连到新的节点cur上,直到到达root或者有重复的点了(需要裂开那个点)
{
next[p][x]=cur; p=fa[p];
}
if(p==0) fa[cur]=root;//后缀找到根,说明不需要裂开就处理完了全部的更新
else
{
int q=next[p][x];//找到那个需要裂开的节点设为q
if(len[q]==len[p]+1) fa[cur]=q;//裂开的点q为p的fa的话,直接将当前点cur的fa连接到q上
else
{
int tmp = newnode(len[p]+1);//tmp是新开的裂开出来的点
memcpy(next[tmp],next[q],sizeof(next[q]));//将q的next数组全部
fa[tmp]=fa[q]; fa[q]=fa[cur]=tmp;
while(p!=0&&next[p][x]==q){
next[p][x]=tmp; p=fa[p];
}
}
}
last=cur;//cur就是插入新字符构造出的新节点。val(cur)的值就是加入这个新的字符出现的新的子串的数量。
}
void top_sort()
{
for(int i=1;i<=tot;i++) ++c[len[i]];
for(int i=1;i<=tot;i++) c[i]+=c[i-1];
for(int i=1;i<=tot;i++) id[c[len[i]]--]=i;
}//id数组就是每一个拓扑序对应的点的下标
//每次遍历拓扑序都是从tot到root1的遍历,遍历时用某个点更新其fa节点的值。有时也会从前到后遍历。
void work(int n)//长度为n的s字符串对每个节点匹配的最大值。
{
int v=1,l=0,c;
for(int i=0;i<n;i++)
{
int c=s[i]-'a';
while(v!=0&&next[v][c]==0) v=fa[v],l=len[v];
if(v!=0)
{
l++,v=next[v][c],mx[v]=max(mx[v],l);
}
else v=1,l=0;
}
}
void get_size()
{
for(int i=tot;i>=1;i--)
{
int v=id[i];
size[fa[v]]+=size[v];
}
//size[1]=0;
//1节点是空串,有的时候不统计时将其定为0;
}
ll cal()//边计算size边求ans,ans是子串中所有任意两个后缀的最长公共前缀的长度和。
{
ll ans=0;
for(int i=tot;i>=1;i--)
{
int v=id[i];
ans+=(ll)size[fa[v]]*size[v]*len[fa[v]];
size[fa[v]]+=size[v];
}
return ans;
}
}sam;//now从1开始的
后缀树的建立:
原串的后缀树就是其反串的后缀自动机的parent树。
回文自动机模板:
//也是一位一位的从第0位插入
//tot-2是回文串的数量
//maxn要开的比字符串长度大几倍
struct PAM{
int next[maxn][26],fail[maxn],len[maxn];
int txt[maxn];
int tot,root0,root1,last,size;
void init(){
last=tot=size=0; txt[size]=-1;
root0=newnode(0); root1=newnode(-1);
fail[root0]=1; fail[root1]=0;
}
int newnode(int l){
len[tot]=l;
memset(next[tot],0,sizeof(next[tot]));
tot++; return tot-1;
}
int getfail(int x){
while(txt[size-len[x]-1]!=txt[size]) x=fail[x];
return x;
}
void extend(int c){
txt[++size]=c; int now=getfail(last);
if(!next[now][c]){
int tmp=newnode(len[now]+2);
fail[tmp]=next[getfail(fail[now])][c];
next[now][c]=tmp;
}
last=next[now][c];
}
}pam;
优先队列优化dij的最短路
struct node
{
int to;
int len;
};
vector<node> edge[maxn];
ll dis[maxn];
int vis[maxn];
p_queue<pair<int, int> > pq;//小根堆,前面的int是dist相反数,后面是节点编号
void dij(int s)
{
for(int i=1;i<=n;i++)
{
dis[i]=INF;
}
memset(vis,0,sizeof(vis));
dis[s]=0;
pq.push(make_pair(0,s));
while(!pq.empty())
{
int x=pq.top().second;
pq.pop();
if(vis[x]) continue;
vis[x]=1;
for(int i=0;i<edge[x].size();i++)
{
int y=edge[x][i].to;
if(dis[y]>dis[x]+edge[x][i].len)//松弛
{
dis[y]=dis[x]+edge[x][i].len;
pq.push(make_pair(-dis[y],y));
}
}
}
}
线段树
#define lson id<<1
#define rson id<<1|1
int arr[maxn];
struct node
{
int left,right;
int mx,mi;
ll sum;
ll lazy;
}tree[(maxn+5)*5];
void push_up(int id)//left和right是不用更新的
{
tree[id].sum=tree[lson].sum+tree[rson].sum;
tree[id].mx=max(tree[lson].mx,tree[rson].mx);
tree[id].mi=min(tree[lson].mi,tree[rson].mi);
}
void push_down(int id)
{
if (tree[id].lazy)
{
tree[lson].lazy = tree[id].lazy;//如果是区间加上val就用+=val
tree[rson].lazy = tree[id].lazy;//如果是区间加上val就用+=val
//{ // 维护最大最小值
// tree[lson].val += tree[rt].lazy;
// tree[rson].val += tree[rt].lazy;
//}
{
int l=tree[id].left,r=tree[id].right;
int mid=l+r>>1;
tree[lson].sum=1ll*(mid-l+1)*tree[id].lazy;//如果是区间加上val就用+=val
tree[rson].sum=1ll*(r-mid)*tree[id].lazy;//如果是区间加上val就用+=val
}
tree[id].lazy=0;
}
}
void build(int id,int l,int r)//建树,build(1,1,n)
{
tree[id].left=l,tree[id].right=r;
if(l==r)
{
tree[id].sum=tree[id].mx=tree[id].mi=arr[l];
return;
}
else
{
int mid=(l+r)/2;
build(lson,l,mid);//左
build(rson,mid+1,r);//右
/* tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
tree[id].mx=max(tree[id*2].mx,tree[id*2+1].mx); */
push_up(id);
tree[id].lazy=0;
}
}
void update_point(int id, int pos, ll val) //pos一点的更新
{
if (tree[id].left==tree[id].right&&pos==tree[id].left)
{
tree[id].sum=val;
tree[id].mx=max(tree[id].mx,(int)val);
tree[id].mi=min(tree[id].mi,(int)val);
return;
}
int mid=(tree[id].left+tree[id].right)/2;
if (pos <= mid) update_point(lson,pos,val);
else update_point(rson,pos,val);
push_up(id);
}
void update(int id, int l, int r, ll val) //l到r区间的值全部变成val的线段树维护
{
// 区间更新
if (l<=tree[id].left&&r>=tree[id].right) //修改的区间覆盖住当前区间
{
tree[id].lazy=val;//如果是区间加上val就用+=val
tree[id].mx=val; //更新时mx和mi怎么更新??
tree[id].mi=val;// 维护最大最小值
tree[id].sum=1ll*(tree[id].right-tree[id].left+1)*val;//如果是区间加上val就用+=val
return;
}
push_down(id);
int mid=(tree[id].left+tree[id].right)/2;
if (l<=mid) update(lson,l,r,val);
if (r>mid) update(rson,l,r,val);
push_up(id);
}
ll query_point(int id, int pos)
{
// 单点查询
if (tree[id].left == tree[id].right && tree[id].left == pos)
return tree[id].sum;
//return tree[id].mx;
//return tree[id].mi
push_down(id);
int mid=(tree[id].left+tree[id].right)/2;
if (pos <= mid) query_point(lson, pos);
else query_point(rson, pos);
}
ll query(int id, int l, int r) //查询l到r区间内的值
{
// 区间查询
if (l <= tree[id].left&&r>=tree[id].right)
return tree[id].sum;
//rerurn tree[id].mx;
//return tree[id].mi;
push_down(id);
int mid=(tree[id].left+tree[id].right)/2;
if (r<=mid) return query(lson, l, r);
else if (l>mid) return query(rson, l, r);
else
{
//return min(query(lson, l, mid), query(rson, mid + 1, r));
//return max(query(lson, l, mid), query(rson, mid + 1, r));
return query(lson, l, mid) + query(rson, mid + 1, r);
}
}
st表模板
//一维
int lg[maxn],fmi[20][maxn];
void st(int n)
{
lg[0]=-1;
for(int i=1;i<maxn;i++)
{
lg[i]=((i&(i-1))==0)? lg[i-1]+1:lg[i-1];
}
for(int i=1;i<=n;i++)
fmi[0][i]=arr[i];//st函数中的数组的导入位置 //对什么数组建立st表就把这个二维数组的每一位初始化成什么/第一项的插入位置
for(int j=1;(1<<j)<=n;j++)//for(int j=1;j<20;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
{
fmi[j][i]=min(fmi[j-1][i],fmi[j-1][i+(1<<j-1)]);
}
}
int ask_min(int x,int y)//查询的区间包括了x和y
{
int k=lg[y-x+1];
return min(fmi[k][x],fmi[k][y-(1<<k)+1]);
}
//二维st表
int h[maxn][maxn];
int lg[maxn],p[20];
int fmx[20][20][maxn][maxn],fmi[20][20][maxn][maxn];
void pre()
{
lg[0]=-1,p[0]=1;
for(int i=1;i<=19;i++) p[i]=p[i-1]<<1;
for(int i=1;i<maxn;i++) lg[i]=lg[i>>1]+1;
}
void get_st(int n,int m)//对1~n,1~m的h处理
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
fmi[0][0][i][j]=fmx[0][0][i][j]=h[i][j];//要预处理的二维数组
for(int ii=0;p[ii]<=n;ii++)
for(int jj=0;p[jj]<=m;jj++)
{
if(ii==0&&jj==0) continue;
for(int i=1;i+p[ii]-1<=n;i++)
for(int j=1;j+p[jj]-1<=m;j++)
{
if(ii)
{
fmi[ii][jj][i][j] = min(fmi[ii-1][jj][i][j],fmi[ii-1][jj][i+p[ii-1]][j]);
fmx[ii][jj][i][j] = max(fmx[ii-1][jj][i][j],fmx[ii-1][jj][i+p[ii-1]][j]);
}
else
{
fmi[ii][jj][i][j] = min(fmi[ii][jj-1][i][j],fmi[ii][jj-1][i][j+p[jj-1]]);
fmx[ii][jj][i][j] = max(fmx[ii][jj-1][i][j],fmx[ii][jj-1][i][j+p[jj-1]]);
}
}
}
}
int ask_max(int x1,int y1,int x2,int y2)//询问(x1,y1)到(x2,y2)的矩形区间内最大值
{
int r=lg[x2-x1+1],c=lg[y2-y1+1];
int a=x2-(1<<r)+1,b=y2-(1<<c)+1;
return max(max(fmx[r][c][x1][y1],fmx[r][c][x1][b]),max(fmx[r][c][a][y1],fmx[r][c][a][b]));
}
int ask_min(int x1,int y1,int x2,int y2)//询问(x1,y1)到(x2,y2)的矩形区间内最小值
{
int r=lg[x2-x1+1],c=lg[y2-y1+1];
int a=x2-(1<<r)+1,b=y2-(1<<c)+1;
return min(min(fmi[r][c][x1][y1],fmi[r][c][x1][b]),min(fmi[r][c][a][y1],fmi[r][c][a][b]));
}
//网络流费用流模板spfa版
#include
using namespace std;
const int maxn=5050,maxm=50050;
const int INF=0x3f3f3f3f;
int n,m,s,t,maxflow,mincost;
int N;
int head[maxn],tot;
struct node
{
int to,next,w,f;
//w是容量,f是费用。
}edge[maxm*2];
inline void add_edge(int u,int v,int w,int f)
{
edge[tot]=(node){
v,head[u],w,f};
head[u]=tot++;
edge[tot]=(node){
u,head[v],0,-f};
head[v]=tot++;
}
inline void edge_init()
{
tot=0;N=n;
memset(head,-1,sizeof(head));
}
int dis[maxn],pre[maxn],minw[maxn];
bool vis[maxn];
bool spfa()
{
queue<int> que;
for(int i=0;i<=N;++i)
{
dis[i]=INF;pre[i]=-1;vis[i]=0;minw[i]=0;
}
que.push(s),dis[s]=0,vis[s]=1,minw[s]=INF;
while(!que.empty())
{
int u=que.front();que.pop();vis[u]=0;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(edge[i].w==0) continue;
if(dis[u]+edge[i].f<dis[v])
{
dis[v]=dis[u]+edge[i].f,pre[v]=i,minw[v]=min(minw[u],edge[i].w);
if(!vis[v]) que.push(v),vis[v]=1;
}
}
}
return dis[t]!=INF;
}
void update()
{
int u=t;
maxflow+=minw[t],mincost+=dis[t]*minw[t];
while(u!=s) edge[pre[u]].w-=minw[t],edge[pre[u]^1].w+=minw[t],u=edge[pre[u]^1].to;
}
void solve()
{
while(spfa()) update();
}
int main()
{
scanf("%d%d%d%d",&n,&m,&s,&t);
edge_init();
for(int i=1;i<=m;i++)
{
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
add_edge(a,b,c,d);
}
solve();
printf("%d %d\n",maxflow,mincost);
return 0;
}
//网络流最大流模板
#include
using namespace std;
const int maxn=10010,maxm=100010;
const int INF=0x3f3f3f3f;
int n,m,s,t;
int head[maxn],tot,N;
int dis[maxn];
struct node
{
int to,next,w;
}edge[2*maxm];//边开大一点,正反向,两倍最好。
void edge_init()
{
memset(head,-1,sizeof(head));
tot=0;N=n;
}
void add_edge(int u,int v,int w)//建边正反向都建边
{
edge[tot]=(node){
v,head[u],w};
head[u]=tot++;
edge[tot]=(node){
u,head[v],0};
head[v]=tot++;
}
int bfs()
{
for(int i=0;i<=N;i++)
{
dis[i]=0;
}
dis[s]=1;
queue<int> que;
que.push(s);
while(!que.empty())
{
int u=que.front();que.pop();
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(edge[i].w>0&&dis[v]==0)
{
dis[v]=dis[u]+1;que.push(v);
}
}
}
if(dis[t]!=0) return 1;
else return 0;
}
int dfs(int u,int dist)
{
if(u==t) return dist;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(dis[v]==dis[u]+1&&edge[i].w!=0)
{
int mi=dfs(v,min(dist,edge[i].w));
if(mi>0)
{
edge[i].w-=mi;
edge[i^1].w+=mi;
return mi;
}
}
}
return 0;
}
int dinic()//dinic模板
{
int maxflow=0;
while(bfs())
{
while(int mi=dfs(s,INF)) maxflow+=mi;
}
return maxflow;
}
int main()
{
scanf("%d%d%d%d",&n,&m,&s,&t);
edge_init();
for(int i=1;i<=m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add_edge(a,b,c);
}
int fin=dinic();
printf("%d\n",fin);
return 0;
}
//字符串中的最小表示法和最大表示法(从某个字符开始的循环同构的字典序的最小或者最大):
最小表示法板子:
int min_show()
{
//s是求解的数组,n是数组的长度,返回值是最小表示法的首字母位置。
//且数组中的元素都是从第0位开始。
int n=strlen(s);
int k=0,i=0,j=1;
while(k<n&&i<n&&j<n)
{
if(s[(i+k)%n]==s[(j+k)%n]) k++;
else
{
s[(i+k)%n]>s[(j+k)%n] ? i=i+k+1 : j=j+k+1;
if(i==j) i++;
k=0;
}
}
return min(i,j);
}
最大表示法板子:
int max_show()
{
//s是求解的数组,n是数组的长度,返回值是最小表示法的首字母位置。
//且数组中的元素都是从第0位开始。
int k=0,i=0,j=1;
int n=strlen(s);
while (i<n&&j<n&&k<n)
{
if(s[(i+k)%n]<s[(j+k)%n])
{
i+=k+1;
if(i==j) i++;k=0;
}
else if(s[(i+k)%n]>s[(j+k)%n])
{
j+=k+1;
if(i==j) j++;k=0;
}
else k++;
}
return min(i,j);
}
//组合数计算模板
//小数据时处理可以先求出多个数的阶乘来求解。
void get_luc(ll mod)
{
luc[0]=1;
for(int i=1;i<maxn;i++) luc[i]=(luc[i-1]*i)%mod;
}
ll qk_power(ll a,ll b,ll c)
{
ll ans=1;
while(b)
{
if(b&1) ans=(ans*a)%c;
b=b>>1;
a=(a*a)%c;
}
return ans;
}
ll lucas(ll n,ll m,ll p)
{
ll ans=1;
while(n&&m)
{
ll a=n%p;ll b=m%p;
if(a<b) return 0;
ans=((ans*luc[a]%p)*(qk_power(luc[b]*luc[a-b]%p,p-2,p)))%p;
n/=p;m/=p;
}
return ans;
}
//大数据要用真正的lucas来求解。(预处理都会超时的情况下或者别的不适合的情况)
ll mulit(ll a,ll b,ll m){
ll ans=0;
while(b){
if(b&1) ans=(ans+a)%m;
a=(a<<1)%m;
b>>=1;
}
return ans;
}
ll quick_mod(ll a,ll b,ll m){
ll ans=1;
while(b){
if(b&1){
ans=mulit(ans,a,m);
}
a=mulit(a,a,m);
b>>=1;
}
return ans;
}
ll comp(ll a,ll b,ll m){
if(a<b) return 0;
if(a==b) return 1;
if(b>a-b) b=a-b;
ll ans=1,ca=1,cb=1;
for(int i=0;i<b;i++){
ca=ca*(a-i)%m;
cb=cb*(b-i)%m;
}
ans=ca*quick_mod(cb,m-2,m)%m;
return ans;
}
ll lucas(ll a,ll b,ll m){
ll ans=1;
while(a&&b){
ans=(ans*comp(a%m,b%m,m))%m;
a/=m;
b/=m;
}
return ans;
}
AC自动机多模式串匹配模板:
/*
模板运用:第一行输入文本串
然后输入n,表示有n个模式串
接下来n行,每行一个数字和一个模式串,0代表可以重叠部分,1代表不可重叠。
输出就是输出每一个模式串在文本串中出现的次数。
*/
#include
#include
#include
#include
#define MAXN 600000+10
#define INF 0x3f3f3f3f
using namespace std;
int ans[MAXN][2];
int node[100000+10];//记录串在Trie中的结束点
int n;
int op[100000+10];
struct Trie
{
int next[MAXN][26], fail[MAXN];
int pos[MAXN];//记录当前节点的字符在模式串的位置
int last[MAXN];//记录当前节点上一个匹配的位置
int L, root;
int newnode()
{
for(int i = 0; i < 26; i++)
next[L][i] = -1;
//End[L++] = 0;
pos[L++] = 0;
return L-1;
}
void init()
{
L = 0;
root = newnode();
}
void Insert(char *s, int id)
{
int now = root;
for(int i = 0; s[i]; i++)
{
if(next[now][s[i]-'a'] == -1)
next[now][s[i]-'a'] = newnode();
now = next[now][s[i]-'a'];
pos[now] = i+1;
}
node[id] = now;//记录串结束点
}
void Build()
{
queue<int> Q;
fail[root] = root;
for(int i = 0; i < 26; i++)
{
if(next[root][i] == -1)
next[root][i] = root;
else
{
fail[next[root][i]] = root;
Q.push(next[root][i]);
}
}
while(!Q.empty())
{
int now = Q.front();
Q.pop();
for(int i = 0; i < 26; i++)
{
if(next[now][i] == -1)
next[now][i] = next[fail[now]][i];
else
{
fail[next[now][i]] = next[fail[now]][i];
Q.push(next[now][i]);
}
}
}
}
void solve(char *s)
{
memset(last, -1, sizeof(last));
memset(ans, 0, sizeof(ans));
int len = strlen(s);
int now = root;
for(int i = 0; i < len; i++)
{
now = next[now][s[i]-'a'];
int temp = now;
while(temp != root)
{
ans[temp][0]++;
if(i - last[temp] >= pos[temp])
{
ans[temp][1]++;
last[temp] = i;
}
temp = fail[temp];
}
}
}
};
Trie ac;
char str[100000+10];
char s[10];
int main()
{
int k = 1;
while(scanf("%s", str) != EOF)
{
ac.init(); scanf("%d", &n);
for(int i = 0; i < n; i++)
{
scanf("%d%s", &op[i], s);
ac.Insert(s, i);
}
ac.Build(); ac.solve(str);
printf("Case %d\n", k++);
for(int i = 0; i < n; i++)
printf("%d\n", ans[node[i]][op[i]]);
//ans[node[i]][0]是可重叠的匹配答案,ans[node[i]][1]是不可重叠的匹配答案。
printf("\n");
}
return 0;
}
/*
Sample Input
ab
2
0 ab
1 ab
abababac
2
0 aba
1 aba
abcdefghijklmnopqrstuvwxyz
3
0 abc
1 def
1 jmn
Sample Output
Case 1
1
1
Case 2
3
2
Case 3
1
1
0
*/
博弈—sg函数:
/*
1.巴什博奕:一堆石头,一次能取1个到m个之间,判断先后手必胜与必败如何判断。
显然如果石头数量是m+1的倍数,那么先手必败,否则先手必胜。
可以拿刚好m+1个石头来思考模拟一下。
代码:
int bash_game(int n,int m)//n堆石头,一次最多拿m个
{
if(n%(m+1)==0) return 1;
else return 0;
}
返回值是1则先手必胜,是0则后手必胜。
2.尼姆博弈:有n堆石头,每堆石头都有着自己的数量。每个人一次可以从一堆石头中
拿走至少一个石头,至多所有石头,询问先手的后手的必胜和必败情况。
sg函数的特殊情况,这种取法没堆石头的sg值就是自己的数目。
所以求其Nim和(将所有堆石头的数量都异或在一起),就是最后先手的必胜必败态了。
如果异或和是0,那么先手必败,如果异或和不为0,那么就是先手必胜。
△:游戏和sg函数等于各个游戏sg函数的Nim和。
3.Anti-Nim博弈(反Nim博弈)
这种博弈形式和普通的Nim博弈一样,只是要求是拿走最后一个石头的人是输。
这种博弈叫作Anti-Nim博弈,也是需要将所有数字异或一下。
先手胜利的条件:
(1).所有堆石子数都为1且游戏的sg值为0;
(2).存在某堆石子的数目大于1并且游戏的sg值不为0。
4.sg函数都介绍与求法:
(1).mex运算,mex运算是对于一个集合而言的,就是求最小的不在该集合中的非负整数。
(2).对于每一个状态的sg函数的求解方法:
找到这个状态进行一次操作后的所有的后继状态,这些后继状态的sg函数组成一个S集合,
这个S集合的mex值就是当前状态的sg值。
一个组合游戏的胜负就是将所有的小游戏的胜负状态求出来,然后求一个Nim和。
如果一个单独游戏到第n个状态时结束,那么这个游戏的第n个状态的sg值决定了这个游戏的胜负。
(3).求解sg函数的值往往都是从小数字的状态推到大数字的状态,先确定几个小数字的必败态,记其
sg函数值为0,然后对于每一个大数字求解其sg函数。
此时大状态的后续状态一定是比其小的数字了,而比当前状态小的状态的sg值早已求出来了,所以可以利用
它们来帮助求解。
5.Chomp!游戏
例1、有一个n*m的棋盘,每次可以取走一个方格并拿掉它右边和上边的所有方格。拿到左下角的格子(1,1)者输,那么谁会赢呢?
solve:除了n=m=1的情况,先手必赢。(右上角的方格特殊)
例2:有n张卡片,第i张纸片上写着数字i,每次可以取走一张纸片和数字是该纸片因数的纸片,那么谁会赢呢?
solve:除了n=1的情况,先手必赢。
例3、现在有一颗含n个节点的有根树,根节点是1,每个点初始时都为白色。每次可以把任意一个白色节点到根节点经过的所有的点(包括该节点)变成黑色。谁最后把整棵树染黑了,谁就输了。
solve: 除了n=1的情况,先手必赢。
6.约数游戏
Describetion:桌子上有n个数字,1~n,两个人轮流选择一个桌上的数x,然后将x与x的约数都拿走,拿去最后一个数的人胜出(无法选择数字的人失败)。
solve: 先手必胜
7.威佐夫博弈
有两堆各若干个物品,两个人轮流从任一堆取至少一个或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
#include
#define endl ‘\n’
using namespace std;
typedef long long ll;
int wegame(int a,int b)
{
int x=min(a,b);
int y=max(a,b);
int k=y-x;
double zzz=(sqrt(5.0)+1)/2;
// cout<
else return 1;
}
int main()
{
std::ios::sync_with_stdio(false);
int a,b;
while(cin>>a>>b)
{
int flag=wegame(a,b);
cout<
return 0;
}
sg函数专题:
Nim游戏是组合游戏的一种,简单的说,符合以下几种条件:
1.有两名选手;2.两名选手交替对游戏进行移动,每次一步,选手可在有限的合法移动集合中任选一种进行移动;
3.对于游戏的任何一种可能的局面,合法的移动集合只取决于这个局面本身,不取决于轮到哪名选手操作和之前的操作和什么别的因素。
4.如果轮到某一名选手移动,并且这个局面的合法的移动集合为空(即无法进行移动),则这名选手负。
sg函数的性质:对于一个游戏的一个局面,如果该点的sg函数值不为0,则该局面是必胜态,否则该局面就是必败态。
初始化时将所有状态的sg值都初始化为0。
对于一个点的sg函数的求解方法:sg(x)=mex{sg(y)|y是x的后继者}.
即:一个点的sg函数是其所有后继的状态的异或值组成的集合的mex值。
求解一个点的sg函数是将其所有的子状态找出来,每个子状态可能对应很多的组合游戏,这些组合游戏对应的所有点的异或值求出来的值就是该点要找的
后继状态的集合中的一个元素。
求解sg函数:
1.for循环对于每一个点遍历,然后for循环对于每一个点的子状态找出来,将每一个子状态对应的所有的组合游戏的sg值异或在一起然后标记放入vis数组中。
2.for循环从0到无穷大枚举j寻找,看该值在vis数组中标记出现过吗,没出现过则该点的sg值就是那个j,然后break。
即: sg(x)=mex{sg(y)|y是x的后继者}.求解时从y推向x,就是对于每一个知道sg值的点,往其后续上面推sg值。
*/
//板子:
sg函数模板(曹老板版):
const int maxn=1e5+10;
const int N=100;
//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在get_sg之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合
int f[N],sg[maxn],S[maxn];
void get_sg(int n)
{
int i,j;
memset(sg,0,sizeof(sg));
//因为SG[0]始终等于0,所以i从1开始
for(i = 1; i <= n; i++)
{
//每一次都要将上一状态 的 后继集合 重置
memset(S,0,sizeof(S));
for(j = 0; f[j] <= i && j <= N; j++)
S[sg[i-f[j]]] = 1; //将后继状态的SG函数值进行标记
for(j = 0;; j++) if(!S[j])
{
//查询当前后继状态SG值中最小的非零值
sg[i] = j;
break;
}
}
}
//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合
int f[N],SG[MAXN],S[MAXN];
void SG(int n)
{
int i,j;
memset(SG,0,sizeof(SG));
//因为SG[0]始终等于0,所以i从1开始
for(i=1;i<=n;i++){
//每一次都要将上一状态的后继集合重置
memset(S,0,sizeof(S));
for(j=0;f[j]<=i&&j<=N;j++)
S[SG[i-f[j]]]=1; //将后继状态的SG函数值进行标记
for(j=0;;j++)
if(!S[j]){
//查询当前后继状态SG值中最小的非零值
SG[i]=j; break;
}
}
}