混进省队后的进化系统
发布日期:2022-02-07 06:39:40 浏览次数:5 分类:技术文章

本文共 18636 字,大约阅读时间需要 62 分钟。

总算是有惊无险的混进省队啦,这代表着我不用滚回去上课辣!

但是像我这种傻叉肯定是不行哒,于是需要一个进化系统!
(觉得像我这种人太弱,就大体上设定一个进化方向,不要限定时间啦…)

Task1:动态树分治

BZOJ3435: [Wc2014]紫荆花之恋
BZOJ3924: [Zjoi2015]幻想乡战略游戏
BZOJ4012: [HNOI2015]开店

Task2:经典分块

BZOJ4028: [HEOI2015]公约数数列

事实上我并不知道我的是不是正解.感觉上单点的话好像是过不去= =.

这题的关键在于注意到一个性质:即前缀最大公约数至多只有 O(logAi) 种.
这并不难理解,因为每一段的值都是前一段的不相等的约数,因此至多为原来的 12 .
那么对于gcd相等的一段,我们就很容易考虑了.
直接利用分块,块内维护前缀和组成的Trie树,在叶子节点上记录一下最小的下标,然后询问到这一块的时候,只需要将要询问的东西异或上前面所有块的异或和就行了.
有点意识流,不懂的看代码吧.
感觉时间复杂度爆炸,不是正解.
利用线段树维护区间gcd,然后修改 O(log2n+nlogAi) ,查询 O(log2Ain) ,真心不知道我是怎么过的…
然后空间上要内存回收,这样空间复杂度就能做到 O(nlogAi) .
代码在这里:

#include
#include
#include
#include
#include
#include
#include
using namespace std;typedef long long ll;#define N 100010#define inf ~0U>>1int n,d[N];inline int gcd(int a,int b){ return(!b)?a:gcd(b,a%b);}struct SegmentTree{ int a[262144],M; inline void init(int _siz){ for(M=1;M<(_siz+2);M<<=1); for(int i=1;i<=_siz;++i) a[M+i]=d[i]; for(int i=M-1;i>=1;--i) a[i]=gcd(a[i<<1],a[i<<1^1]); } inline int query(int tl,int tr){ int re=0; for(tl+=M-1,tr+=M+1;tl^tr^1;tl>>=1,tr>>=1){ if(~tl&1) re=gcd(a[tl^1],re); if(tr&1) re=gcd(a[tr^1],re); } return re; } inline void modify(int ins,int c){ for(a[ins+=M]=c,ins>>=1;ins;ins>>=1) a[ins]=gcd(a[ins<<1],a[ins<<1^1]); }}seg;int num;int begin[110],end[110],_gcd[110];inline void build_gcd(){ int i=1,now_gcd,L,R,mid; num=0; while(i<=n){ now_gcd=seg.query(1,i); L=i,R=n; while(L
>1; if(seg.query(1,mid)==now_gcd) L=mid; else R=mid-1; } ++num; begin[num]=i; end[num]=L; _gcd[num]=now_gcd; i=L+1; }}struct Node{ int l,r,v;}S[5000010];int cnt;stack
bin;inline void dfs_recover(int q){ if(S[q].l) dfs_recover(S[q].l); if(S[q].r) dfs_recover(S[q].r); S[q].l=S[q].r=S[q].v=0; bin.push(q);}inline int newnode(){ if(bin.empty()) return ++cnt; else{ int get=bin.top(); bin.pop(); return get; }}struct Block{ int begin,end,root,sum;}B[1010];int belong[N];inline void insert(int&q,int v,int dep,int lab){ if(!q) q=newnode(); if(dep<0){ if(S[q].v==0) S[q].v=lab; return; } if((v>>dep)&1) insert(S[q].r,v,dep-1,lab); else insert(S[q].l,v,dep-1,lab);}inline int get(int q,int v,int dep){ if(!q) return inf; if(dep<0) return S[q].v; if((v>>dep)&1) return get(S[q].r,v,dep-1); else return get(S[q].l,v,dep-1);}int get(int l,int r,ll x){ if(x>=1ll<<30) return inf; int pre=0,now,ans=inf; for(int i=1;i
=l&&i<=r) ans=min(ans,i); for(int i=belong[l];i
=l&&i<=r) ans=min(ans,i); pre=0; for(int i=1;i<=belong[l];++i) pre^=B[i].sum; for(int i=belong[l]+1;i
0); for(i=1;i<=block_num;++i){ B[i].begin=B[i-1].end+1; B[i].end=min(n,B[i].begin+block_siz-1); for(j=B[i].begin;j<=B[i].end;++j){ belong[j]=i; insert(B[i].root,B[i].sum^=d[j],29,j); } } int m; scanf("%d",&m); char qte[10]; ll x; int ins,c; while(m--){ scanf("%s",qte); if(qte[0]=='Q'){ scanf("%lld",&x); int ans=inf; for(i=1;i<=num;++i) if(x%_gcd[i]==0) ans=min(ans,get(begin[i],end[i],x/_gcd[i])); if(ans==inf) puts("no"); else printf("%d\n",ans-1); } else{ scanf("%d%d",&ins,&c); seg.modify(++ins,c); d[ins]=c; build_gcd(); ins=belong[ins]; dfs_recover(B[ins].root); B[ins].root=0; B[ins].sum=0; for(j=B[ins].begin;j<=B[ins].end;++j) insert(B[ins].root,B[ins].sum^=d[j],29,j); } } return 0;}

Task3:网络流模型

Task4:树的性质

BZOJ3624: [Apio2008]免费道路

题意就是构造一颗生成树,使得关键边恰有 k 条.

分别按照优先选择关键边以及优先选择非关键边,得到合法生成树中关键便边数的范围.
这样就能判定是否有解.
然后我们得到关键边最少的那颗生成树,抽出那些关键边,然后将剩下的关键边尝试加入进去,直到关键边恰为
k
条.
然后把所有的非关键边插入,直到形成一颗完整的生成树.
这样构造的正确性很显然:对于关键边最少的那颗生成树,其中的关键边可以保证剩下的非关键边插入后能形成生成树,那么我们再插入一些关键边必然依旧能够满足这个性质.
利用并查集随便搞搞就行啦.

BZOJ4015: [FJOI2014]树的重心

总算是自己把树的重心的性质又挖掘了一遍.

什么是树的重心?
就是删除这个点之后,剩下的最大的连通分量最小.这就是重心.
(另外可以证明,对于重心而言,删除之后剩下的最大的连通分量不超过原来的一半,不过取不取等号我就不是非常清楚了.)
一棵树有两个重心,当且仅当这两个重心相邻,并且将这条边割去,剩下的两个连通分量大小相同.
一棵树有且仅有一个重心,令这棵树的总点数为 n ,当且仅当删去这个点之后最大的连通分量的大小
s
满足 2s<n .
有了以上性质之后,这道题目就能够用背包随便搞搞就行啦.
放一下非常丑的代码:

#include
#include
#include
#include
#include
#include
using namespace std;#define inf 0x1f1f1f1f#define N 210int head[N],next[N<<1],end[N<<1],ind;inline void reset(){ ind=0; memset(head,0,sizeof head);}inline void addedge(int a,int b){ int q=++ind; end[q]=b; next[q]=head[a]; head[a]=q;}inline void make(int a,int b){ addedge(a,b); addedge(b,a);}int q[N],fr,ta,pa[N],siz[N];vector
gravity;static const int mod=10007;inline void inc(int&x,int y){ if((x+=y)>=mod) x-=mod;}namespace Solve1{ int f[N][N],g[N],pa[N],siz[N]; inline void dfs(int x){ siz[x]=1; for(int j=head[x];j;j=next[j]) if(end[j]!=pa[x]){ pa[end[j]]=x; dfs(end[j]); siz[x]+=siz[end[j]]; } } inline void dp(int x){ int i,j,k; for(j=head[x];j;j=next[j]) if(end[j]!=pa[x]) dp(end[j]); f[x][1]=1; memset(g,0,sizeof g); g[0]=1; for(j=head[x];j;j=next[j]){ if(end[j]!=pa[x]){ for(k=siz[x]-1;k>=0;--k) for(i=1;i<=siz[end[j]]&&i<=k;++i) inc(g[k],g[k-i]*f[end[j]][i]%mod); } } for(i=1;i
=0;--k) for(int i=1;i<=siz[end[j]]&&(i<<1)
<=k;++i) inc(g[k],g[k-i]*f[end[j]][i]%mod); } inc(res,g[all-1]); } printf("%d\n",res); }}namespace Solve2{ int f[N][N],g[N],siz[N]; inline void dp(int x,int fa){ int i,j,k; siz[x]=1; for(j=head[x];j;j=next[j]) if(end[j]!=fa){ dp(end[j],x); siz[x]+=siz[end[j]]; } f[x][1]=1; memset(g,0,sizeof g); g[0]=1; for(j=head[x];j;j=next[j]) if(end[j]!=fa){ for(k=siz[x]-1;k>=0;--k) for(i=1;i<=siz[end[j]]&&i<=k;++i) inc(g[k],g[k-i]*f[end[j]][i]%mod); } for(i=1;i
>T; int n,i,j,a,b; for(int Tcase=1;Tcase<=T;++Tcase){ printf("Case %d: ",Tcase); scanf("%d",&n); reset(); for(i=1;i

Task5:数位dp

Task6:概率与期望

BZOJ4008: [HNOI2015]亚瑟王

我这种脑残连这种题都不会做啦…

将问题转化为将 r 次机会顺次分给
n
张纸牌的问题.
fi,j 表示已经正要考虑第 i 张卡牌,还剩
j
次机会的概率.
那么有 fi,j=fi1,j(1pi1)j+fi1,j+1+(1(1pi1)j+1)
显然递归边界为 f1,r=1
所以答案就是:

i=1nj=1rfi,jdi(1(1pi)j)

BZOJ3586: 字符串生成器
BZOJ3317: [Swerc2008]First Knight

Task7:奇怪的贪心

BZOJ1150: [CTSC2007]数据备份Backup
BZOJ2151: 种树
BZOJ4027: [HEOI2015]兔子与樱花

这个贪心很早就想出来了.

首先是考虑所有的叶子,若他可以合并到父节点上,那么必须现在就合并,否则当这个点被接到某个深度更小的祖先之后,这个祖先的樱花数必定不小于父节点的樱花数,因此更加不可能合并.如果不可能合并,我们就将这个点附加到父节点的樱花数中去,容易证明这是等价的.
其次,一个节点的孩子合并到自身,不会对自己的父亲造成影响,这是显然的.
最后,为了保证删除的数目最多,显然应该按照樱花数升序删除.
(想出正解程序写错,没治了>_<.)

#include
#include
#include
#include
#include
using namespace std;#define N 2000010int c[N],deg[N],pa[N],q[N],tq[N],tcnt,cnt;inline bool cmp(const int&x,const int&y){ return c[x]
v[N];int sav[N],num;int main(){ int n,m; scanf("%d%d",&n,&m); int i,j; for(i=0;i

Task8:FFT专场

BZOJ3456: 城市规划

容易将递推式变成这种形式:

fn=Cni=1n1fiAni
不妨利用cdq分治,递归计算完左侧的值之后考虑左侧对于右侧的贡献,发现是一个卷积.
于是可以在
O(nlog2n)
时间内解决.
有一个多项式求逆的方法,但是局限性比较大,而且我并没有看懂,于是就先挖坑.
upd:其实好像并不是很容易?
注意这里的城市是两两不同的,其实就是:
fn=2(n2)i=1n1fi2(ni2)(n1i1)
我们可以形象的理解为枚举其中特定的某个点所在的连通分量的大小,这样可以求出所有不连通图的个数,然后再用总数减去就行啦.
然后变形:
fn=2(n2)i=1n1fi2(ni2)(n1)!(i1)!(ni)!
fn=2(n2)(n1)!i=1n1fi(i1)!2(ni2)(ni)!
然后就很水了吧.
但是我的代码依然非常慢…

#include
#include
#include
#include
#include
#include
using namespace std;typedef long long ll;static const int p=(479<<21)|1;static const int g=3;inline int ksm(int x,ll y){ int re=1,t=x; for(;y;y>>=1,t=(ll)t*t%p) if(y&1) re=(ll)re*t%p; return re;}inline int inv(int x){ return ksm(x,p-2);}inline int get_g(){ static int sav[110]; int num=0,tmp=p-1; for(int i=2;i*i<=p-1;++i){ if(tmp%i==0){ sav[++num]=i; while(tmp%i==0) tmp/=i; } } if(tmp!=1) sav[++num]=tmp; for(int i=1;;++i){ bool ok=1; for(int j=1;j<=num;++j) if(ksm(i,(p-1)/sav[j])==1){ ok=0; break; } if(ok) return i; }}vector
v[21];inline void bit_init(){ int i,j,k,tmp; for(i=1;i<=20;++i){ for(j=0;j<(1<
>k)&1) tmp|=1<<(i-k-1); v[i].push_back(tmp); } }}inline void ntt(int A[],int n,bool rev){ static int B[262144]; int i,j,k,bit=0; for(int tmp=n;tmp!=1;tmp>>=1,++bit); for(i=0;i
>1);++j,w=(ll)w*wn%p){ t=(ll)w*A[i+j+(k>>1)]%p; A[i+j+(k>>1)]=(A[i+j]+p-t)%p; (A[i+j]+=t)%=p; } if(rev){ int inv_n=inv(n); for(i=0;i
>1)+p-f[l])%p; return; } int mid=(l+r)>>1; solve(l,mid); int M=1; for(M=1;M<=(r-l+1);M<<=1); for(int i=0;i
>1)*rfac[i]%p; solve(1,n); printf("%d",f[n]); return 0;}
BZOJ3509: [CodeChef] COUNTARI

我这种傻逼真是没治了,这种东西都想不出来.

发现各种不可做,于是可以分块T^T.
发现三个数可能的分布情况只有以下几种:
{
a,a,a},{
a,a,b},{
a,b,b},{
a,b,c}
每一块有 size 个节点,前三种情况可以 O(nsizesize2=nsize) 暴力.
对于最后一种情况,枚举中间点所在的块,对两边进行卷积就行了.
这一步复杂度为 O(nsizeAilogAi) .
因此总时间复杂度为 O(nsize+nsizeAilogAi) .
size=AilogAi 时,显然复杂度取得最小值 O(nAilogAi) .
能有这种思路也真是神奇…
upd:现在又wa又t一时爽,等要来数据之后再说吧.
upd:本机12s,交上去40sTLE已精神AC.

Nescafe41 ProblemA 异化多肽 多项式求逆

题意见.

一眼生成函数.
显然就是搞出多项式 A ,求
i=0Ai
xn 的系数.
然后根据等比数列求和公式,这东西就是(由于是形式幂级数,所以可以令 |x|<1 ):

1A1A=11A
显然求个在模
xn+1
意义下的逆元就行啦.
由于常系数不为0,因此肯定存在逆元.
然后求逆元利用倍增算法,时间复杂度为
O(nlogn)
.
(我一开始是求
ni=1Ai
xn
的系数,然后利用等比数列需要求出
An+1
,然后我写了一个傻逼的快速幂,就变成了
O(nlog2n)
…)
然后多项式逆元3次NTT就行啦!(窝大傻叉以前都是5次…)
丢代码跑.

#include
#include
#include
#include
#include
#include
using namespace std;typedef long long ll;static const int p=1005060097;static const int g=5;#define N 262144inline int ksm(int x,int y){ int re=1,t=x; for(;y;y>>=1,t=(ll)t*t%p) if(y&1) re=(ll)re*t%p; return re;}inline int inv(int x){ return ksm(x,p-2);}inline void inc(int&x,int y){ if((x+=y)>=p) x-=p;}inline void dec(int&x,int y){ if((x-=y)<0) x+=p;}vector
v[19];inline void bit_init(){ int i,j,k,tmp; for(i=1;i<=18;++i){ for(j=0;j<(1<
>k)&1) tmp|=1<<(i-k-1); v[i].push_back(tmp); } }}inline void ntt(int A[],int n,bool rev){ static int B[N]; int i,j,k,bit=0; for(int tmp=n;tmp!=1;tmp>>=1,++bit); for(i=0;i
>1;++j,w=(ll)w*wn%p){ t=(ll)w*A[i+j+(k>>1)]%p; A[i+j+(k>>1)]=(A[i+j]+p-t)%p; (A[i+j]+=t)%=p; } if(rev){ int inv_n=inv(n); for(i=0;i
>1);++i) nB[i]=B[i]; for(i=0;i
cf472G

各种不可做,显然应该分块.

但是我没想出来如何分块.
首先容易想到将B串倒过来求卷积,然后发现下标和相同的对正好就是两个连续子串,可惜这样子是会重复的.
如果每次询问的都是A自己而不是A的子串的话就不会重复了.
所以对于A分块,对于每一块对于B的反串求一遍卷积,然后就行了.
真心好思路~~~.

cf528D

考虑对于一个起点位置,满足条件当且仅当有很多段区间,其中某些区间必须有一个A,某些区间必须有一个G(这些区间的长度都是 2k+1 ),以此类推.

不妨单独考虑某个字母,那么我们首先应该知道所有长度为 2k+1 的区间是不是至少有一个该字母.根据上一题的做法,我们直接构造一个长度为 2k+1 的该字母字符串,并和原串的反串做卷积即可.
这样就知道所有区间开头合不合适啦!
但是我们并不知道某个起点是不是合法的.
我们利用Bitset搞搞就行啦!
对于四个字母分别搞出所有合法的起点,扫一遍就行啦!

Task9:动态二分图

表示终于学会支持离线的动态二分图啦!

预处理出每一条边的删除时间,如果是加边的话利用动态树维护关于删除时间的最大生成树,那么如果两个点已经连通,要删除一条边,如果加入这条边形成了奇环,我们就将删掉的这条边加入一个集合;要删除一条边,若在集合中直接删去,若在树中也直接删去.
那么某一个时刻这个图为二分图当且仅当这个时刻集合为空.
于是就变成了LCT裸题了.

BZOJ4025: 二分图

就在这里把日记补一补吧…

2015/4/24

说起来又是好久都没有写日记了呢…

对于JLOI的题目我就不多说啦,毕竟都是大水题,结果最后我却只有那点可怜的分数…

树上的路径包含问题总结

主要是看下面的这道题目了吧.

BZOJ3772: 精神污染
主要有两种方法,对于每条路径,计算这条路径覆盖了多少条子路径.
这个分类讨论一下就行了.
我们预处理一下这棵树的入栈出栈序.
如果这条路径是从上( a )到下(
b
)的一条链,令 c
b
的深度大于 a 的深度最小的祖先,则我们建两个矩形,
[x[1,inc1],y[inb,outb]],[x[outc+1,n],y[inb,outb]]
,显然这两个矩形是没有交的.
当然这有一个条件,那就是 ab .
a=b 的时候,这就是一个点…貌似利用dfs序就很不容易搞定了.
否则这条链是分岔的,那么一个矩形就可搞定, [x[ina,outa],y[inb,outb]] .
所以,当所有被覆盖的路径都满足 ab 的时候,我们就可以搞出这些矩形,再离线利用扫描线搞搞就行了.
将每一条路径看作一个点,那么这条路径覆盖的路径条数就是这个点在多少个矩形中.
由于有可能是交换之后出现在矩形中,对于一个点我们插入两次(注意 a=b 这种路径答案肯定是 0 ),并将矩形覆盖数求和即可.
注意这种算法虽然有限制,但是时间复杂度是可以做到只与询问数有关的.
另外一种思路比较巧妙:
我们注意到一条路径是另外一条路径的子路径当且仅当这条路径的两个端点都在另外一条路径上.
于是,对于每一条路径,我们都固定一个端点,并在这个端点上存下另外一个端点.
那么,问一条路径覆盖了多少条子路径,等价于问这条路径上所有的点对应的所有另外的端点有多少依然在这条路径上.
于是随便利用什么数据结构搞搞都行了.(这个暂时不知道支不支持复杂度与询问相关)

BZOJ3997: [TJOI2015]组合数学

感觉对于偏序集的各种东西了解的不是非常清楚.
一种偏序需要满足的条件是:自反性,反对称性,传递性.
然后一条链指的就是若干的有序的元素,任意连续两个元素都满足序关系,因此他们是一种全序关系,也就是任意两个均可以进行比较.
注意反链的定义:反链就是若干个元素,其中任意两个元素均没有偏序关系.
然后就是Dirworth定理:最小链覆盖等于最长的反链长度.
清楚了这些之后随便dp一下就行了.
BZOJ1273: [BeiJingWc2008]序列
这道题目思路还是不错的.
关键在于,我们考虑第
i
个二进制位,若每次加法只加 1 ,从
0
开始加,那么每进行 2i 次加法,这一个二进制位才会发生变化.并且是 01 的交替变化.
因此我们对每一个二进制位维护一个模意义下的有序序列即可.
时间复杂度 O(nlogn) .
BZOJ3219: 巡游
这题我傻逼,一开始觉得二分答案是错的,于是就没有往那里去想T^T.
但是显然可以二分答案啊.
f(w) 表示一条中位数大小至少为 w ,且边数符合要求的路径的存在性,不妨令
w1>w2
,那么如果 f(w1)=1 ,这条路径本身的存在一定也会使得 f(w2)=1 ,因此,我们直接二分就好了.
判定这个问题,我们可以做一次点分治.对于 w ,我们将树上的所有边重赋值,若边权
w
,则权值为 1 ,否则权值为
1
.我们不难发现,若树上存在一条长度合法且权值和 0 的路径, f(w) 就为 1 ,否则
f(w)
0 .
于是就
O(nlog2n)
过了.
以前没有尝试过在分治内部二分的过程,这次希望试一试T^T.
BZOJ4017: 小Q的无敌异或
这道题觉得太恶心实现不下去,决定有时间思路清晰了再写.
第一问就很简单了,二进制拆分后转化为问有多少区间有奇数个 1 ,简单的dp就行了.
对于第二问,考察区间终点固定的所有和,按照BZOJ1273的思路,对于每一个二进制位维护一个有序序列即可,但是要支持动态插入,因此需要维护一颗平衡树,但是太恶心了写到一半就扔了…

2015/4/27

BZOJ4032: [HEOI2015]最短不公共子串

看起来不太容易,不过想想就是水题了.
对于前两问直接二分+hash就行了.
对于后两问维护一个自动机,并在自动机节点上存下A串中所有走到这个节点长度为
i
的子序列,其结尾位置在A串中最早出现的位置.然后当长度为 i+1 时,扫描所有自动机上的节点,并向下转移就行了.当发现某个节点转移不出去的时候直接返回当前长度就行了.于是时间复杂度 O(n2) .
说的不太明白,具体看代码吧.

#include
#include
#include
#include
#include
#include
using namespace std;typedef unsigned long long llu;static const int seed=113;#define inf 0x3f3f3f3f#define N 2010char s1[N],s2[N];int l1,l2;llu f1[N],f2[N],pow[N];llu gethash1(int l,int r){ return f1[l]-f1[r+1]*pow[r-l+1];}llu gethash2(int l,int r){ return f2[l]-f2[r+1]*pow[r-l+1];}int next1[N][26],next2[N][26];namespace Task1{ set
s; inline int solve(){ int L=1,R=l1+1,mid,i,j; while(L
>1; s.clear(); for(i=1;i+mid-1<=l2;++i) s.insert(gethash2(i,i+mid-1)); bool allfind=1; for(i=1;i+mid-1<=l1;++i) if(s.find(gethash1(i,i+mid-1))==s.end()) allfind=0; if(allfind) L=mid+1; else R=mid; } if(L>l1) return -1; return L; }}namespace Task2{ inline int solve(){ int L=1,R=l1+1,mid,i,j; while(L
>1; bool allok=1; for(i=1;i+mid-1<=l1;++i){ int p=0; bool find=1; for(j=1;j<=mid;++j){ if(next2[p][s1[i+j-1]-'a'+1]==0){ find=0; break; } else p=next2[p][s1[i+j-1]-'a'+1]; } if(!find) allok=0; } if(allok) L=mid+1; else R=mid; } if(L>l1) return -1; return L; }}namespace Task3{ int pa[N<<1],tr[N<<1][27],len[N<<1],d[2][N<<1],cnt,root,last; inline int newnode(int _len){ len[++cnt]=_len; return cnt; } inline int solve(){ root=last=newnode(0); int p,np,q,nq,y,i,j; for(i=1;i<=l2;++i){ np=newnode(len[last]+1); y=s2[i]-'a'+1; for(p=last;p&&!tr[p][y];p=pa[p]) tr[p][y]=np; if(!p) pa[np]=root; else{ q=tr[p][y]; if(len[q]==len[p]+1) pa[np]=q; else{ nq=newnode(len[p]+1); pa[nq]=pa[q]; pa[q]=pa[np]=nq; memcpy(tr[nq],tr[q],sizeof tr[q]); for(;p&&tr[p][y]==q;p=pa[p]) tr[p][y]=nq; } } last=np; } int now=0,pre=1; memset(d[now],0x3f,sizeof d[now]); d[now][root]=0; for(int len=1;len<=l1;++len){ bool notfind=0; swap(now,pre); memset(d[now],0x3f,sizeof d[now]); for(i=1;i<=cnt;++i){ if(d[pre][i]!=inf){ for(j=1;j<=26;++j){ if(next1[d[pre][i]][j]!=0){ if(tr[i][j]==0) notfind=1; else d[now][tr[i][j]]=min(d[now][tr[i][j]],next1[d[pre][i]][j]); } } } } if(notfind==1) return len; } return -1; }}namespace Task4{ int d[2][N]; inline int solve(){ int now=0,pre=1,i,j; memset(d[now],0x3f,sizeof d[now]); d[now][0]=0; for(int len=1;len<=l1;++len){ swap(now,pre); memset(d[now],0x3f,sizeof d[now]); bool notfind=0; for(i=0;i<=l2;++i){ if(d[pre][i]!=inf){ for(j=1;j<=26;++j){ if(next1[d[pre][i]][j]!=0){ if(next2[i][j]==0) notfind=1; else d[now][next2[i][j]]=min(d[now][next2[i][j]],next1[d[pre][i]][j]); } } } } if(notfind) return len; } return -1; }}int main(){ scanf("%s%s",s1+1,s2+1); l1=strlen(s1+1); l2=strlen(s2+1); int i,j; for(pow[0]=1,i=1;i<=2000;++i) pow[i]=pow[i-1]*seed; for(i=l1;i>=1;--i) f1[i]=f1[i+1]*seed+(s1[i]-'a'+1); for(i=l2;i>=1;--i) f2[i]=f2[i+1]*seed+(s2[i]-'a'+1); for(i=0;i<=l1;++i) for(j=i+1;j<=l1;++j) if(next1[i][s1[j]-'a'+1]==0) next1[i][s1[j]-'a'+1]=j; for(i=0;i<=l2;++i) for(j=i+1;j<=l2;++j) if(next2[i][s2[j]-'a'+1]==0) next2[i][s2[j]-'a'+1]=j; printf("%d\n",Task1::solve()); printf("%d\n",Task2::solve()); printf("%d\n",Task3::solve()); printf("%d\n",Task4::solve()); return 0;}

BZOJ4031: [HEOI2015]小Z的房间

牛逼姿势get√
直接就能进行模意义下的高斯消元啦!
但是,需要付出一个 log 的代价.
其实也并没有什么好说了,看代码一眼明了.

#include
#include
#include
#include
#include
#include
using namespace std;typedef long long ll;typedef long double db;bool map[10][10];char s[10];int A[110][110],ch[10][10],B[110][110];const int dx[]={-1,1,0,0},dy[]={ 0,0,-1,1};#define inrange(x,l,r) ((x)>=(l)&&(x)<=(r))static const int mod=1000000000;int get(int n){ int i,j,k,p,ans=1; for(i=1;i<=n;++i){ for(k=i;k<=n;++k) if(B[k][i]) break; if(k>n) return 0; if(k!=i){ ans*=-1; for(p=1;p<=n;++p) swap(B[i][p],B[k][p]); } for(k=i+1;k<=n;++k){ while(1){ int tmp=(mod-B[k][i]/B[i][i])%mod; for(p=i;p<=n;++p) B[k][p]=(B[k][p]+(ll)tmp*B[i][p])%mod; if(!B[k][i]) break; ans*=-1; for(p=i;p<=n;++p) swap(B[i][p],B[k][p]); } } } for(i=1;i<=n;++i) ans=(ll)ans*B[i][i]%mod; if(ans<0) ans+=mod; return ans;}int main(){ int n,m; scanf("%d%d",&n,&m); int i,j,k; for(i=1;i<=n;++i){ scanf("%s",s+1); for(j=1;j<=m;++j) map[i][j]=(s[j]=='.'); } int id=0; for(i=1;i<=n;++i) for(j=1;j<=m;++j) if(map[i][j]) ch[i][j]=++id; for(i=1;i<=n;++i) for(j=1;j<=m;++j) if(map[i][j]){ int from=ch[i][j]; for(k=0;k<4;++k){ if(inrange(i+dx[k],1,n)&&inrange(j+dy[k],1,m)&&map[i+dx[k]][j+dy[k]]){ int to=ch[i+dx[k]][j+dy[k]]; ++A[from][from]; --A[from][to]; } } } for(i=2;i<=id;++i) for(j=2;j<=id;++j) B[i-1][j-1]=A[i][j]; cout<

2015/4/28

一般是求质数的原根吧,我们的方法无非就是枚举原根了,因为一般都很小.

但是之前我只会 O(n) 的判定,十分是太傻叉了.
下面介绍一种单组 O(log2n) 的判定方法.
首先考虑数 n ,我们考虑与
n
互质且 <n <script type="math/tex" id="MathJax-Element-86"> ϕ(n) 个数,由欧拉定理我们有 aϕ(n)1(mod n) ,对于数 a ,我们令
a
的指标 ind(a) 表示一个最小的正整数 k ,使得
ak1(mod n)
.
于是显然不是每个数的指标都是 n1 啦.
比如对于 n=7,a=2 ,我们发现 a31(mod n) ,于是 ind(2)=3 .
假设对于某个数 a ,有
ind(a)<n1
,则我们可以证明 ind(a)|n1 .
利用反证法:若 (n1)%ind(a)>0 ,由 aind(a)an11(mod n) ,可以发现 a(n1)%ind(a)1(mod n) .而 (n1)%ind(a)<ind(a) 且满足 ind(a) 的性质,与假设矛盾.故证毕.
(以上的证明忽略了某些特殊情况,但不影响大局.)
然后如果对于某个 a
ind(a)=n1
,我们就把这样的 a 叫做
n
的原根.
原根具有很好的性质,我们很容易发现 a,a2,a3,...an1 在模 n 的意义下恰好遍历了所有
ϕ(n)
个与 n 互质且
<n
的的数.
那么我们如何判断一个数 a 是不是
n
的原根呢?
那么我们就是要看是否对于所有 n1 的与自身不相同的正约数 t ,均不满足
at1(mod n)
.
若存在的话显然就不是了.
我们可以直接暴力枚举所有的约数,这样单组判定是 O(nlogn) 的.
但是我们可以注意到若 at1(mod n) ,则对于 kN ,必有 akt1(mod n) ,这指引我们尽可能去判断那些比较大的约数,因为他们更可能满足条件.
不是自身的且尽可能大的约数比较少,令 n1 的质因数分解为 mi=1pqii ,则我们只需判定 n1pi 这样 O(logn) 个约数是不是满足条件就行了.
容易证明,若他们均不满足条件,所有的约数一定都不满足条件.
于是这样单组只需要 O(log2n) .
BZOJ3236: [Ahoi2013]作业
第一问,我们可以用主席树直接搞,时间复杂度 O(mlogn) .
第二问,我们可以离线之后用树状数组套主席树搞,时间复杂度 O(mlog2n) ,空间复杂度为 O(nlog2n) ,但是还是不虚的.
怎么离线就不说了,很简单的.
BZOJ4036: [HAOI2015]Set
口胡了一个不知道对不对的题解.
首先令 Fi,s 表示进行 i 次选择,集合至多为
s
的概率, fi,s 表示集合恰为 s 的概率.
显然:

Fi,s=ssfi,s
gs
表示一次选择选到的集合均是
s
子集的概率.
则有:
Fi,s=gis
由集合反演得到:

fi,s=ss(1)|s||s|gis
令全集为
S
,我们要求的其实就是
fi,S
,不妨写作
fi
.
显然有:
fi=s(1)n|s|gis
注意到答案就是求
f1+2(f2f1)+3(f3f2)+......
.
不妨仅考察前
m
项,发现结果就是
mfmm1i=1fi
.
不妨对于每一个集合进行考虑,结果就是:
s(1)n|s|(mgmsj=1m1gjs)
由于
gs<=1
,当
m
趋于
mgms
趋于
0
,所以答案就很显而易见了.如果是
gs=1
时特判一下就行啦!
我们刚才其实忽略了一个很严重的问题!
g
数组应该如何求!
显然可以
O(3n)
暴力!但是这样就超时辣!
有一个我不太明白的
O(n2n)
的算法,详情见代码.
然后再特判一下INF的情况.
这样就完美解决啦!
upd:没有spj也A给你看!
upd:膜拜莫比乌斯变换,坐等vfk论文!

#include
#include
#include
#include
#include
#include
using namespace std;typedef double db;int bitcnt[1<<20];db g[1<<20];int main(){ int n; scanf("%d",&n); int i,j; for(i=1;i<(1<
0) all|=i; } if(all!=(1<
>i)&1)==0) g[j^(1<

转载地址:https://blog.csdn.net/wyfcyx_forever/article/details/45245819 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:docker安装oracle
下一篇:Ubuntu 16.04 几个国内更新源

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2024年04月02日 15时00分45秒