混进省队后的进化系统

总算是有惊无险的混进省队啦,这代表着我不用滚回去上课辣!
但是像我这种傻叉肯定是不行哒,于是需要一个进化系统!
(觉得像我这种人太弱,就大体上设定一个进化方向,不要限定时间啦…)

Task1:动态树分治
BZOJ3435: [Wc2014]紫荆花之恋
BZOJ3924: [Zjoi2015]幻想乡战略游戏
BZOJ4012: [HNOI2015]开店
Task2:经典分块
BZOJ4028: [HEOI2015]公约数数列

事实上我并不知道我的是不是正解.感觉上单点的话好像是过不去= =.
这题的关键在于注意到一个性质:即前缀最大公约数至多只有 O(logAi) 种.
这并不难理解,因为每一段的值都是前一段的不相等的约数,因此至多为原来的 12 .
那么对于gcd相等的一段,我们就很容易考虑了.
直接利用分块,块内维护前缀和组成的Trie树,在叶子节点上记录一下最小的下标,然后询问到这一块的时候,只需要将要询问的东西异或上前面所有块的异或和就行了.
有点意识流,不懂的看代码吧.
感觉时间复杂度爆炸,不是正解.
利用线段树维护区间gcd,然后修改 O(log2n+nlogAi) ,查询 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 条.
分别按照优先选择关键边以及优先选择非关键边,得到合法生成树中关键便边数的范围.
这样就能判定是否有解.
然后我们得到关键边最少的那颗生成树,抽出那些关键边,然后将剩下的关键边尝试加入进去,直到关键边恰为 k 条.
然后把所有的非关键边插入,直到形成一颗完整的生成树.
这样构造的正确性很显然:对于关键边最少的那颗生成树,其中的关键边可以保证剩下的非关键边插入后能形成生成树,那么我们再插入一些关键边必然依旧能够满足这个性质.
利用并查集随便搞搞就行啦.

BZOJ4015: [FJOI2014]树的重心

总算是自己把树的重心的性质又挖掘了一遍.
什么是树的重心?
就是删除这个点之后,剩下的最大的连通分量最小.这就是重心.
(另外可以证明,对于重心而言,删除之后剩下的最大的连通分量不超过原来的一半,不过取不取等号我就不是非常清楚了.)
一棵树有两个重心,当且仅当这两个重心相邻,并且将这条边割去,剩下的两个连通分量大小相同.
一棵树有且仅有一个重心,令这棵树的总点数为 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;
}
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<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",&deg[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: 城市规划

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

fn=Cni=1n1fiAni

不妨利用cdq分治,递归计算完左侧的值之后考虑左侧对于右侧的贡献,发现是一个卷积.
于是可以在 O(nlog2n) 时间内解决.
有一个多项式求逆的方法,但是局限性比较大,而且我并没有看懂,于是就先挖坑.
upd:其实好像并不是很容易?
注意这里的城市是两两不同的,其实就是:
fn=2(n2)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值