总算是有惊无险的混进省队啦,这代表着我不用滚回去上课辣!
但是像我这种傻叉肯定是不行哒,于是需要一个进化系统!
(觉得像我这种人太弱,就大体上设定一个进化方向,不要限定时间啦…)
Task1:动态树分治
BZOJ3435: [Wc2014]紫荆花之恋
BZOJ3924: [Zjoi2015]幻想乡战略游戏
BZOJ4012: [HNOI2015]开店
Task2:经典分块
BZOJ4028: [HEOI2015]公约数数列
事实上我并不知道我的是不是正解.感觉上单点的话好像是过不去= =.
这题的关键在于注意到一个性质:即前缀最大公约数至多只有 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;
}
Task3:网络流模型
Task4:树的性质
BZOJ3624: [Apio2008]免费道路
题意就是构造一颗生成树,使得关键边恰有 k 条.
分别按照优先选择关键边以及优先选择非关键边,得到合法生成树中关键便边数的范围.
这样就能判定是否有解.
然后我们得到关键边最少的那颗生成树,抽出那些关键边,然后将剩下的关键边尝试加入进去,直到关键边恰为
然后把所有的非关键边插入,直到形成一颗完整的生成树.
这样构造的正确性很显然:对于关键边最少的那颗生成树,其中的关键边可以保证剩下的非关键边插入后能形成生成树,那么我们再插入一些关键边必然依旧能够满足这个性质.
利用并查集随便搞搞就行啦.
BZOJ4015: [FJOI2014]树的重心
总算是自己把树的重心的性质又挖掘了一遍.
什么是树的重心?
就是删除这个点之后,剩下的最大的连通分量最小.这就是重心.
(另外可以证明,对于重心而言,删除之后剩下的最大的连通分量不超过原来的一半,不过取不取等号我就不是非常清楚了.)
一棵树有两个重心,当且仅当这两个重心相邻,并且将这条边割去,剩下的两个连通分量大小相同.
一棵树有且仅有一个重心,令这棵树的总点数为 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;
}
Task5:数位dp
Task6:概率与期望
BZOJ4008: [HNOI2015]亚瑟王
我这种脑残连这种题都不会做啦…
将问题转化为将 r 次机会顺次分给
令 fi,j 表示已经正要考虑第 i 张卡牌,还剩
那么有 fi,j=fi−1,j⋅(1−pi−1)j+fi−1,j+1+(1−(1−pi−1)j+1)
显然递归边界为 f1,r=1
所以答案就是:
BZOJ3586: 字符串生成器
BZOJ3317: [Swerc2008]First Knight
Task7:奇怪的贪心
BZOJ1150: [CTSC2007]数据备份Backup
BZOJ2151: 种树
BZOJ4027: [HEOI2015]兔子与樱花
这个贪心很早就想出来了.
首先是考虑所有的叶子,若他可以合并到父节点上,那么必须现在就合并,否则当这个点被接到某个深度更小的祖先之后,这个祖先的樱花数必定不小于父节点的樱花数,因此更加不可能合并.如果不可能合并,我们就将这个点附加到父节点的樱花数中去,容易证明这是等价的.
其次,一个节点的孩子合并到自身,不会对自己的父亲造成影响,这是显然的.
最后,为了保证删除的数目最多,显然应该按照樱花数升序删除.
(想出正解程序写错,没治了>_<.)
#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;
}
Task8:FFT专场
BZOJ3456: 城市规划
容易将递推式变成这种形式:
不妨利用cdq分治,递归计算完左侧的值之后考虑左侧对于右侧的贡献,发现是一个卷积.
于是可以在 O(nlog2n) 时间内解决.
有一个多项式求逆的方法,但是局限性比较大,而且我并没有看懂,于是就先挖坑.
upd:其实好像并不是很容易?
注意这里的城市是两两不同的,其实就是: