总算是有惊无险的混进省队啦,这代表着我不用滚回去上课辣!
但是像我这种傻叉肯定是不行哒,于是需要一个进化系统!
(觉得像我这种人太弱,就大体上设定一个进化方向,不要限定时间啦…)
事实上我并不知道我的是不是正解.感觉上单点的话好像是过不去= =.
这题的关键在于注意到一个性质:即前缀最大公约数至多只有 O(logAi) 种.
这并不难理解,因为每一段的值都是前一段的不相等的约数,因此至多为原来的 12 .
那么对于gcd相等的一段,我们就很容易考虑了.
直接利用分块,块内维护前缀和组成的Trie树,在叶子节点上记录一下最小的下标,然后询问到这一块的时候,只需要将要询问的东西异或上前面所有块的异或和就行了.
有点意识流,不懂的看代码吧.
感觉时间复杂度爆炸,不是正解.
利用线段树维护区间gcd,然后修改 O(log2n+n−−√logAi) ,查询 O(log2Ain−−√) ,真心不知道我是怎么过的…
然后空间上要内存回收,这样空间复杂度就能做到 O(nlogAi) .
代码在这里:
#include<cstdio>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<stack>
using namespace std;
typedef long long ll;
#define N 100010
#define inf ~0U>>1
int 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<R){
mid=(L+R+1)>>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<int>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<belong[l];++i)
pre^=B[i].sum;
now=pre;
for(int i=B[belong[l]].begin;i<=B[belong[l]].end;++i)
if((now^=d[i])==x&&i>=l&&i<=r)
ans=min(ans,i);
for(int i=belong[l];i<belong[r];++i)
pre^=B[i].sum;
now=pre;
for(int i=B[belong[r]].begin;i<=B[belong[r]].end;++i)
if((now^=d[i])==x&&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<belong[r];++i){
ans=min(ans,get(B[i].root,x^pre,29));
pre^=B[i].sum;
}
return ans;
}
int main(){
scanf("%d",&n);
int i,j;
for(i=1;i<=n;++i)
scanf("%d",&d[i]);
seg.init(n);
build_gcd();
int block_siz=(int)sqrt(n);
int block_num=n/block_siz+(n%block_siz>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;
}
题意就是构造一颗生成树,使得关键边恰有 k 条.
分别按照优先选择关键边以及优先选择非关键边,得到合法生成树中关键便边数的范围.
这样就能判定是否有解.
然后我们得到关键边最少的那颗生成树,抽出那些关键边,然后将剩下的关键边尝试加入进去,直到关键边恰为 k 条.
然后把所有的非关键边插入,直到形成一颗完整的生成树.
这样构造的正确性很显然:对于关键边最少的那颗生成树,其中的关键边可以保证剩下的非关键边插入后能形成生成树,那么我们再插入一些关键边必然依旧能够满足这个性质.
利用并查集随便搞搞就行啦.
总算是自己把树的重心的性质又挖掘了一遍.
什么是树的重心?
就是删除这个点之后,剩下的最大的连通分量最小.这就是重心.
(另外可以证明,对于重心而言,删除之后剩下的最大的连通分量不超过原来的一半,不过取不取等号我就不是非常清楚了.)
一棵树有两个重心,当且仅当这两个重心相邻,并且将这条边割去,剩下的两个连通分量大小相同.
一棵树有且仅有一个重心,令这棵树的总点数为 n ,当且仅当删去这个点之后最大的连通分量的大小 s 满足 2s<n .
有了以上性质之后,这道题目就能够用背包随便搞搞就行啦.
放一下非常丑的代码:
#include<cstdio>
#include<cstring>
#include<climits>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
#define inf 0x1f1f1f1f
#define N 210
int 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<int>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<siz[x];++i)
f[x][i+1]=g[i];
}
inline void work(int root){
memset(pa,0,sizeof pa);
dfs(root);
memset(f,0,sizeof f);
dp(root);
int res=0;
for(int all=1;all<=siz[root];++all){
memset(g,0,sizeof g);
g[0]=1;
for(int j=head[root];j;j=next[j]){
for(int k=all-1;k>=0;--k)
for(int i=1;i<=siz[end[j]]&&(i<<1)<all&&i<=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<siz[x];++i)
f[x][i+1]=g[i];
}
inline void work(int r1,int r2){
memset(f,0,sizeof f);
dp(r1,r2);
dp(r2,r1);
int res=0;
for(int i=1;i<=siz[r1]&&i<=siz[r2];++i)
inc(res,f[r1][i]*f[r2][i]%mod);
printf("%d\n",res);
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("tt.in","r",stdin);
#endif
int T;
cin>>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<n;++i)
scanf("%d%d",&a,&b),make(a,b);
if(n<=2)
puts("1");
else{
fr=ta=0;
q[ta++]=1;
memset(pa,0,sizeof pa);
while(fr!=ta){
i=q[fr++];
for(j=head[i];j;j=next[j]){
if(end[j]!=pa[i]){
pa[end[j]]=i;
q[ta++]=end[j];
}
}
}
memset(siz,0,sizeof siz);
for(i=ta-1;i;--i)
siz[pa[q[i]]]+=++siz[q[i]];
gravity.clear();
int Min=inf,now;
for(i=1;i<=n;++i){
now=n-siz[i];
for(j=head[i];j;j=next[j])
if(pa[end[j]]==i)
now=max(now,siz[end[j]]);
if(now<Min){
Min=now;
gravity.clear();
gravity.push_back(i);
}
else if(now==Min)
gravity.push_back(i);
}
if(gravity.size()==1)
Solve1::work(gravity[0]);
else
Solve2::work(gravity[0],gravity[1]);
}
}
return 0;
}
我这种脑残连这种题都不会做啦…
将问题转化为将 r 次机会顺次分给 n 张纸牌的问题.
令 fi,j 表示已经正要考虑第 i 张卡牌,还剩 j 次机会的概率.
那么有 fi,j=fi−1,j⋅(1−pi−1)j+fi−1,j+1+(1−(1−pi−1)j+1)
显然递归边界为 f1,r=1
所以答案就是:
这个贪心很早就想出来了.
首先是考虑所有的叶子,若他可以合并到父节点上,那么必须现在就合并,否则当这个点被接到某个深度更小的祖先之后,这个祖先的樱花数必定不小于父节点的樱花数,因此更加不可能合并.如果不可能合并,我们就将这个点附加到父节点的樱花数中去,容易证明这是等价的.
其次,一个节点的孩子合并到自身,不会对自己的父亲造成影响,这是显然的.
最后,为了保证删除的数目最多,显然应该按照樱花数升序删除.
(想出正解程序写错,没治了>_<.)
#include<cstdio>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
#define N 2000010
int c[N],deg[N],pa[N],q[N],tq[N],tcnt,cnt;
inline bool cmp(const int&x,const int&y){
return c[x]<c[y];
}
vector<int>v[N];
int sav[N],num;
int main(){
int n,m;
scanf("%d%d",&n,&m);
int i,j;
for(i=0;i<n;++i)
scanf("%d",&c[i]);
int x;
for(i=0;i<n;++i){
scanf("%d",°[i]);
for(j=1;j<=deg[i];++j)
scanf("%d",&x),pa[x]=i,v[i].push_back(x);
}
int fr=0,ta=0;
for(i=0;i<n;++i)
if(deg[i]==0)
q[ta++]=i;
int ans=0;
while(fr!=ta){
i=q[fr++];
sort(v[i].begin(),v[i].end(),cmp);
for(j=0;j<v[i].size();++j)
if(c[i]+c[v[i][j]]+v[i].size()-1-j<=m)
c[i]+=c[v[i][j]],++ans;
else{
c[i]+=v[i].size()-j;
break;
}
if(!--deg[pa[i]])
q[ta++]=pa[i];
}
printf("%d",ans);
return 0;
}
容易将递推式变成这种形式:
#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<vector>
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<int>v[21];
inline void bit_init(){
int i,j,k,tmp;
for(i=1;i<=20;++i){
for(j=0;j<(1<<i);++j){
tmp=0;
for(k=0;k<i;++k)
if((j>>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<n;++i)
B[i]=A[v[bit][i]];
for(i=0;i<n;++i)
A[i]=B[i];
int w,wn,t;
for(k=2;k<=n;k<<=1)
for(wn=rev?ksm(g,(p-1)-(p-1)/k):ksm(g,(p-1)/k),i=0;i<n;i+=k)
for(w=1,j=0;j<(k>>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<n;++i)
A[i]=(ll)A[i]*inv_n%p;
}
}
int fac[131072],rfac[131072],a[131072];
int f[131072];
inline void solve(int l,int r){
static int A[262144],B[262144];
if(l==r){
f[l]=(ll)f[l]*fac[l-1]%p;
f[l]=(ksm(2,(ll)(l-1)*l>>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<M;++i)
A[i]=B[i]=0;
for(int i=l;i<=mid;++i)
A[i-l+1]=(ll)f[i]*rfac[i-1]%p;
for(int i=1;i<=r-l;++i)
B[i]=a[i];
ntt(A,M,0);
ntt(B,M,0);
for(int i=0;i<M;++i)
A[i]=(ll)A[i]*B[i]%p;
ntt(A,M,1);
for(int i=mid+1;i<=r;++i)
(f[i]+=A[i-l+1])%=p;
solve(mid+1,r);
}
int main(){
bit_init();
int n;
scanf("%d",&n);
int i,j;
for(fac[0]=1,i=1;i<=n;++i)
fac[i]=(ll)fac[i-1]*i%p;
for(i=0;i<=n;++i)
rfac[i]=inv(fac[i]);
for(i=1;i<=n;++i)
a[i]=(ll)ksm(2,(ll)i*(i-1)>>1)*rfac[i]%p;
solve(1,n);
printf("%d",f[n]);
return 0;
}
我这种傻逼真是没治了,这种东西都想不出来.
发现各种不可做,于是可以分块T^T.
发现三个数可能的分布情况只有以下几种:
{a,a,a},{a,a,b},{a,b,b},{a,b,c}
每一块有 size 个节点,前三种情况可以 O(nsize⋅size2=n⋅size) 暴力.
对于最后一种情况,枚举中间点所在的块,对两边进行卷积就行了.
这一步复杂度为 O(nsize⋅AilogAi) .
因此总时间复杂度为 O(n⋅size+nsize⋅AilogAi) .
当 size=AilogAi−−−−−−−√ 时,显然复杂度取得最小值 O(nAilogAi−−−−−−−√) .
能有这种思路也真是神奇…
upd:现在又wa又t一时爽,等要来数据之后再说吧.
upd:本机12s,交上去40sTLE已精神AC.
题意见http://blog.csdn.net/popoqqq/article/details/45335371.
一眼生成函数.
显然就是搞出多项式 A ,求 ∑∞i=0Ai 中 xn 的系数.
然后根据等比数列求和公式,这东西就是(由于是形式幂级数,所以可以令 |x|<1 ):
#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
static const int p=1005060097;
static const int g=5;
#define N 262144
inline 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<int>v[19];
inline void bit_init(){
int i,j,k,tmp;
for(i=1;i<=18;++i){
for(j=0;j<(1<<i);++j){
tmp=0;
for(k=0;k<i;++k)
if((j>>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<n;++i)
B[i]=A[v[bit][i]];
for(i=0;i<n;++i)
A[i]=B[i];
int wn,w,t;
for(k=2;k<=n;k<<=1)
for(wn=rev?ksm(g,(p-1)-(p-1)/k):ksm(g,(p-1)/k),i=0;i<n;i+=k)
for(w=1,j=0;j<k>>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<n;++i)
A[i]=(ll)A[i]*inv_n%p;
}
}
inline void get_inv(int A[],int rA[],int n){
static int B[N],nB[N],nA[N];
int i,k;
for(i=0;i<N;++i)
B[i]=nB[i]=nA[i]=0;
B[0]=inv(A[0]);
for(k=2;k<=n;k<<=1){
for(i=0;i<(k<<1);++i)
nB[i]=nA[i]=0;
for(i=0;i<(k>>1);++i)
nB[i]=B[i];
for(i=0;i<k;++i)
nA[i]=A[i];
ntt(nA,k<<1,0);
ntt(nB,k<<1,0);
for(i=0;i<(k<<1);++i)
nB[i]=(ll)nB[i]*(2+p-(ll)nA[i]*nB[i]%p)%p;
ntt(nB,k<<1,1);
for(i=0;i<k;++i)
B[i]=nB[i];
}
for(i=0;i<n;++i)
rA[i]=B[i];
}
int A[N],inv_A[N],t[N],re[N];
int main(){
bit_init();
freopen("polypeptide.in","r",stdin);
freopen("polypeptide.out","w",stdout);
int n,m;
scanf("%d%d",&n,&m);
int i;
int M=1;
for(;M<n+1;M<<=1);
int x;
while(m--){
scanf("%d",&x);
if(x<=n)
A[x]++;
}
A[0]=1;
for(i=1;i<M;++i)
A[i]=p-A[i];
get_inv(A,inv_A,M);
printf("%d",inv_A[n]);
fclose(stdin);
fclose(stdout);
return 0;
}
各种不可做,显然应该分块.
但是我没想出来如何分块.
首先容易想到将B串倒过来求卷积,然后发现下标和相同的对正好就是两个连续子串,可惜这样子是会重复的.
如果每次询问的都是A自己而不是A的子串的话就不会重复了.
所以对于A分块,对于每一块对于B的反串求一遍卷积,然后就行了.
真心好思路~~~.
考虑对于一个起点位置,满足条件当且仅当有很多段区间,其中某些区间必须有一个A,某些区间必须有一个G(这些区间的长度都是 2k+1 ),以此类推.
不妨单独考虑某个字母,那么我们首先应该知道所有长度为 2k+1 的区间是不是至少有一个该字母.根据上一题的做法,我们直接构造一个长度为 2k+1 的该字母字符串,并和原串的反串做卷积即可.
这样就知道所有区间开头合不合适啦!
但是我们并不知道某个起点是不是合法的.
我们利用Bitset搞搞就行啦!
对于四个字母分别搞出所有合法的起点,扫一遍就行啦!
表示终于学会支持离线的动态二分图啦!
预处理出每一条边的删除时间,如果是加边的话利用动态树维护关于删除时间的最大生成树,那么如果两个点已经连通,要删除一条边,如果加入这条边形成了奇环,我们就将删掉的这条边加入一个集合;要删除一条边,若在集合中直接删去,若在树中也直接删去.
那么某一个时刻这个图为二分图当且仅当这个时刻集合为空.
于是就变成了LCT裸题了.
http://codeforces.com/gym/100513/problem/A
就在这里把日记补一补吧…
说起来又是好久都没有写日记了呢…
对于JLOI的题目我就不多说啦,毕竟都是大水题,结果最后我却只有那点可怜的分数…
主要是看下面的这道题目了吧.
BZOJ3772: 精神污染
主要有两种方法,对于每条路径,计算这条路径覆盖了多少条子路径.
这个分类讨论一下就行了.
我们预处理一下这棵树的入栈出栈序.
如果这条路径是从上( a )到下( b )的一条链,令 c 是 b 的深度大于 a 的深度最小的祖先,则我们建两个矩形, [x∈[1,inc−1],y∈[inb,outb]],[x∈[outc+1,n],y∈[inb,outb]] ,显然这两个矩形是没有交的.
当然这有一个条件,那就是 a≠b .
当 a=b 的时候,这就是一个点…貌似利用dfs序就很不容易搞定了.
否则这条链是分岔的,那么一个矩形就可搞定, [x∈[ina,outa],y∈[inb,outb]] .
所以,当所有被覆盖的路径都满足 a≠b 的时候,我们就可以搞出这些矩形,再离线利用扫描线搞搞就行了.
将每一条路径看作一个点,那么这条路径覆盖的路径条数就是这个点在多少个矩形中.
由于有可能是交换之后出现在矩形中,对于一个点我们插入两次(注意 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的思路,对于每一个二进制位维护一个有序序列即可,但是要支持动态插入,因此需要维护一颗平衡树,但是太恶心了写到一半就扔了…
BZOJ4032: [HEOI2015]最短不公共子串
看起来不太容易,不过想想就是水题了.
对于前两问直接二分+hash就行了.
对于后两问维护一个自动机,并在自动机节点上存下A串中所有走到这个节点长度为 i 的子序列,其结尾位置在A串中最早出现的位置.然后当长度为 i+1 时,扫描所有自动机上的节点,并向下转移就行了.当发现某个节点转移不出去的时候直接返回当前长度就行了.于是时间复杂度 O(n2) .
说的不太明白,具体看代码吧.
#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
typedef unsigned long long llu;
static const int seed=113;
#define inf 0x3f3f3f3f
#define N 2010
char 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<llu>s;
inline int solve(){
int L=1,R=l1+1,mid,i,j;
while(L<R){
mid=(L+R)>>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<R){
mid=(L+R)>>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<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<cmath>
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<<get(id-1);
return 0;
}
一般是求质数的原根吧,我们的方法无非就是枚举原根了,因为一般都很小.
但是之前我只会 O(n) 的判定,十分是太傻叉了.
下面介绍一种单组 O(log2n) 的判定方法.
首先考虑数 n ,我们考虑与 n 互质且 <n 的 ϕ(n) 个数,由欧拉定理我们有 aϕ(n)≡1(mod n) ,对于数 a ,我们令 a 的指标 ind(a) 表示一个最小的正整数 k ,使得 ak≡1(mod n) .
于是显然不是每个数的指标都是 n−1 啦.
比如对于 n=7,a=2 ,我们发现 a3≡1(mod n) ,于是 ind(2)=3 .
假设对于某个数 a ,有 ind(a)<n−1 ,则我们可以证明 ind(a)|n−1 .
利用反证法:若 (n−1)%ind(a)>0 ,由 aind(a)≡an−1≡1(mod n) ,可以发现 a(n−1)%ind(a)≡1(mod n) .而 (n−1)%ind(a)<ind(a) 且满足 ind(a) 的性质,与假设矛盾.故证毕.
(以上的证明忽略了某些特殊情况,但不影响大局.)
然后如果对于某个 a 有 ind(a)=n−1 ,我们就把这样的 a 叫做 n 的原根.
原根具有很好的性质,我们很容易发现 a,a2,a3,...an−1 在模 n 的意义下恰好遍历了所有 ϕ(n) 个与 n 互质且 <n 的的数.
那么我们如何判断一个数 a 是不是 n 的原根呢?
那么我们就是要看是否对于所有 n−1 的与自身不相同的正约数 t ,均不满足 at≡1(mod n) .
若存在的话显然就不是了.
我们可以直接暴力枚举所有的约数,这样单组判定是 O(n−−√logn) 的.
但是我们可以注意到若 at≡1(mod n) ,则对于 k∈N∗ ,必有 akt≡1(mod n) ,这指引我们尽可能去判断那些比较大的约数,因为他们更可能满足条件.
不是自身的且尽可能大的约数比较少,令 n−1 的质因数分解为 ∑mi=1pqii ,则我们只需判定 n−1pi 这样 O(logn) 个约数是不是满足条件就行了.
容易证明,若他们均不满足条件,所有的约数一定都不满足条件.
于是这样单组只需要 O(log2n) .
BZOJ3236: [Ahoi2013]作业
第一问,我们可以用主席树直接搞,时间复杂度 O(mlogn) .
第二问,我们可以离线之后用树状数组套主席树搞,时间复杂度 O(mlog2n) ,空间复杂度为 O(nlog2n) ,但是还是不虚的.
怎么离线就不说了,很简单的.
BZOJ4036: [HAOI2015]Set
口胡了一个不知道对不对的题解.
首先令 Fi,s 表示进行 i 次选择,集合至多为 s 的概率, fi,s 表示集合恰为 s 的概率.
显然:
#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<cmath>
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<<n);++i)
bitcnt[i]=bitcnt[i^(i&-i)]+1;
int all=0;
for(i=0;i<(1<<n);++i){
scanf("%lf",&g[i]);
if(g[i]>0)
all|=i;
}
if(all!=(1<<n)-1)
puts("INF");
else{
for(i=0;i<n;++i)
for(j=0;j<(1<<n);++j)
if(((j>>i)&1)==0)
g[j^(1<<i)]+=g[j];
double ans=0;
for(i=0;i<(1<<n);++i){
if((n-bitcnt[i])%2==0)
ans+=(i==(1<<n)-1)?1:-g[i]/(1-g[i]);
else
ans-=(i==(1<<n)-1)?1:-g[i]/(1-g[i]);
}
printf("%.10lf",ans);
}
return 0;
}