2014多校4,hdu4897,hdu4898,hdu4902,hdu4906题解

来源:互联网 发布:弗洛伊德算法 path 编辑:程序博客网 时间:2024/05/17 07:57

多校4:

丽洁大神的思维果然不是一般人能懂。连看个标程都费劲得死。

所以人家能IOI第一呢。

 

本场又被吊打,不过结果是其它队做出来的1009是个错题……

 

好吧来看题目。

1001,hdu4897:树链剖分!为了做这题我才学的!

CLJ的代码风格太过诡异而不能看懂,我参照题解思考了一个算法:

线段树上保存两种标记,标记1是用于操作1,当翻转某条路径时,将该路径的标记1反转,并记录区间和。这个标记是用来表示边的,因此每个边的标记保存在该边两点中更深的一个节点上。

标记2用于操作2,因为一个节点可能连了很多轻链,因此修改节点来代替修改轻链,而重链则直接修改。用一条边的两个节点的标记2是否相同来表示这条边是否被操作2反转。因此对一条路径做操作2就是,就是将路径上重链的标记2反转,并将这条重链链头链尾相邻的重链也修改(修改标记1)。

这样查询某条边是否是黑就是

轻链:a.tag1^a.tag2^b.tag2,a、b是此边的两个端点,dep[a]>dep[b]。(轻链只有两个点!)

重链:用区间求和计算路径上的tag1的和。

链头链尾要小心。

 

#include<iostream>#include<cstdio>#include<cstring>using namespace std;#define ls (p<<1)#define rs (p<<1|1)#define NN 201000int en[NN],fi[NN],te,ne[NN],v[NN];int tp;int siz[NN],son[NN],top[NN],fa[NN],dep[NN],tid[NN],rank[NN];void addedge(int a,int b){    ++te;ne[te]=fi[a];fi[a]=te;v[te]=b;    ++te;ne[te]=fi[b];fi[b]=te;v[te]=a;}void dfs1(int u,int father,int d){    siz[u]=1;    dep[u]=d;    fa[u]=father;    int e,vv;    //printf("%d\n",u);    for(e=fi[u];e!=-1;e=ne[e]){        vv=v[e];        if (vv!=father){            dfs1(vv,u,d+1);            siz[u]+=siz[vv];            if (son[u]==-1||siz[vv]>siz[son[u]]){                son[u]=vv;            }        }    }}void dfs2(int u,int ttop){    top[u]=ttop;    tid[u]=++tp;    rank[tid[u]]=u;    if (son[u]==-1) return;    dfs2(son[u],ttop);    int e,vv;    for(e=fi[u];e!=-1;e=ne[e]){        vv=v[e];        if (vv!=fa[u]&&vv!=son[u]){            dfs2(vv,vv);        }    }}struct segtree{    int l,r,tag1,tag2,rev1,rev2;}t[NN*4];inline void Rev(int p,int func){    if (func==1) {t[p].rev1^=1;t[p].tag1=t[p].r+1-t[p].l-t[p].tag1;}    if (func==2) {t[p].rev2^=1;t[p].tag2^=1;}}void lazy(int p){    if (t[p].l==t[p].r) {t[p].rev1=t[p].rev2=0;return;}    if (t[p].rev1!=0) {        t[p].rev1=0;        Rev(ls,1);        Rev(rs,1);    }    if (t[p].rev2!=0) {        t[p].rev2=0;        Rev(ls,2);        Rev(rs,2);    }}void build(int l,int r,int p){    t[p].l=l;t[p].r=r;    t[p].tag1=t[p].tag2=t[p].rev1=t[p].rev2=0;    if (l==r){return;}    int m=l+r>>1;    build(l,m,ls);    build(m+1,r,rs);}inline void update(int p){    t[p].tag1=t[ls].tag1+t[rs].tag1;}void rev(int l,int r,int func,int p){    if (l>r) return;    if (t[p].l==l&&t[p].r==r){        Rev(p,func);        return;    }    lazy(p);    int m=t[p].l+t[p].r>>1;    if (r<=m) rev(l,r,func,ls);    else if (l>m) rev(l,r,func,rs);    else {        rev(l,m,func,ls);        rev(m+1,r,func,rs);    }    update(p);}int query(int l,int r,int p){    if (l>r) return 0;    if (t[p].l==l&&t[p].r==r){        return t[p].tag1;    }    lazy(p);    int m=t[p].l+t[p].r>>1;    if (r<=m) return query(l,r,ls);    else if (l>m) return query(l,r,rs);    else {        return query(l,m,ls)+query(m+1,r,rs);    }    update(p);}int querytag2(int pos,int p){    if (t[p].l==t[p].r) return t[p].tag2;    lazy(p);    int m=t[p].l+t[p].r>>1;    if (pos<=m) return querytag2(pos,ls);    else return querytag2(pos,rs);    update(p);}void linerev(int a,int b){    while(top[a]!=top[b]){        if (dep[top[a]]<dep[top[b]]) swap(a,b);        rev(tid[top[a]],tid[a],1,1);        a=fa[top[a]];    }    if (dep[a]>dep[b]) swap(a,b);    rev(tid[a]+1,tid[b],1,1);//注意加1,最高点不用改变}void linerevadj(int a,int b){    while(top[a]!=top[b]){        if (dep[top[a]]<dep[top[b]]) swap(a,b);        rev(tid[top[a]],tid[a],2,1);        int faa=fa[top[a]];        if (son[faa]==top[a]) rev(tid[top[a]],tid[top[a]],1,1);        if (son[a]!=-1) rev(tid[son[a]],tid[son[a]],1,1);        a=fa[top[a]];    }    if (dep[a]>dep[b]) swap(a,b);    rev(tid[a],tid[b],2,1);//这里不用加1,路径上每个点都需要更改tag2标记    int faa=fa[a];    if (faa!=0&&son[faa]==a) rev(tid[a],tid[a],1,1);    if (son[b]!=-1) rev(tid[son[b]],tid[son[b]],1,1);}int linequery(int a,int b){    int ret=0;    while(top[a]!=top[b]){        if (dep[top[a]]<dep[top[b]]) swap(a,b);        ret+=query(tid[top[a]]+1,tid[a],1);        ret+=(querytag2(tid[fa[top[a]]],1)^querytag2(tid[top[a]],1)^query(tid[top[a]],tid[top[a]],1));        a=fa[top[a]];    }    if (dep[a]>dep[b]) swap(a,b);    ret+=query(tid[a]+1,tid[b],1);    return ret;}int main(){    int cas,n,q,i,a,b,c;    scanf("%d",&cas);    while(cas--){        scanf("%d",&n);        memset(fi,-1,sizeof(fi));        te=0;        for(i=1;i<n;++i){            scanf("%d%d",&a,&b);            addedge(a,b);        }        memset(son,-1,sizeof(son));        tp=0;        dfs1(1,0,0);        dfs2(1,1);        build(1,tp,1);        scanf("%d",&q);        for(i=1;i<=q;++i){            scanf("%d%d%d",&a,&b,&c);            if (a==1){                if (b==c) continue;                linerev(b,c);            }            else if (a==2){                linerevadj(b,c);            }            else if (a==3){                printf("%d\n",linequery(b,c));            }        }    }    return 0;}

 

1002,hdu4898:这题我几乎是把标程抄了一遍。

尤其是Check()函数我就无耻地直接Copy了一下。

这种让最大值最小、最小值最大的问题常用二分。

再讲一下Check函数的思路(想了好久),一个点可以跳到它的可跳最大值之内的任意点,将不可以往后跳的点都剔除,这样所有点都是连通的,最多可以跳剩下点数a次,最小就是每次跳最大值的次数b。因此k在[b,a]之间都是可行的。

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<vector>using namespace std;#define NN 4100char s[NN];int n,k;int tsub;int lcp[NN][NN];struct Sub{    int l,r;    void init(int _l,int _r){l=_l;r=_r;}    int size() {return r-l+1;}    char charat(int x) {        if (x>r-l)  return 0;        else return s[l+x];    }}sub[NN*NN];int Lcp(Sub a,Sub b){    return min(lcp[a.l][b.l],min(a.size(),b.size()));}bool operator < (Sub a,Sub b){    int m=Lcp(a,b);    return a.charat(m)<b.charat(m);}int step[NN];bool check(Sub M) {vector<int> nxt(n);for (int i = 0; i < n; ++i) {    Sub tmp;    tmp.init(i,i+n-1);int L = Lcp(tmp, M);if (s[(i + L) % n] < M.charat(L))nxt[i] = n;elsenxt[i] = L;}for (;;) {bool done = true;for (int i = 0; i < nxt.size(); ++i) {if (nxt[i] == 0) {for (int j = 0; j < nxt.size(); ++j)if (j != i) {if (j < i && j + nxt[j] >= i)--nxt[j];else if (j > i && j + nxt[j] >= i + nxt.size())--nxt[j];}nxt.erase(nxt.begin() + i);done = false;break;}}if (done)break;}if (k > nxt.size())return false;for (int i = 0; i < nxt.size() * 2; ++i) {step[i] = i + nxt[i % nxt.size()];}for (int i = 0; i < nxt.size(); ++i) {int need = 0, at = i;while (at < i + nxt.size()) {at = step[at];++need;}if (need <= k)return true;}return false;}Sub work(){    int l=1,r=tsub,m,ans=r;    int i,j,tot;    while(l<r){        m=l+r>>1;        Sub a=sub[m];        if (check(a)) {            if (ans>m) ans=m;            r=m-1;        }        else l=m+1;    }    return sub[ans];}void output(Sub a){    int i,r=a.r;    for(i=a.l;i<=a.r;++i){        printf("%c",s[i]);    }    printf("\n");}int main(){    int cas;    scanf("%d",&cas);    int i,j;    while(cas--){        scanf("%d%d",&n,&k);        scanf("%s",s);        for(i=0;i<n;++i){            s[n+i]=s[i];        }        memset(lcp,0,sizeof(lcp));        for(i=n+n-1;i>=0;--i){            for(j=n+n-1;j>=0;--j){                if (s[i]==s[j]) lcp[i][j]=lcp[i+1][j+1]+1;                else lcp[i][j]=0;            }        }        tsub=0;        Sub tmp;        for(i=0;i<n+n;++i){            for(j=i;j<n+n;++j){                if (j-i+1>n) break;                tmp.init(i,j);                sub[++tsub]=tmp;            }        }        sort(sub+1,sub+tsub+1);        tmp=work();        output(tmp);    }    return 0;}


 

1006,hdu4902:线段树!

神奇诡异的证明,反正我是不会。不过看到那么多队伍都通过,就怀疑着写了一发。(居然过了什么的…)

做法看代码吧,用一个标记标记一段相同,用一个标记标记段最大值,如果2操作的值比这段的最大值大,就无视这次操作,否则就暴力往下做。由于gcd是越来越小,而且一个数取gcd最多不超过32次。想想应该操作次数应该不会太多。

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define ls (p<<1)#define rs (p<<1|1)#define NN 101000int val[NN],cas,n;struct tree{    int l,r,ma,res,val;}t[NN*4];void lazy(int p){    if (t[p].res==0) return;    else if (t[p].res==1){        t[p].res=0;        t[p].ma=t[p].val;        if (t[p].l==t[p].r) return;        t[ls].res=1;t[ls].ma=t[ls].val=t[p].val;        t[rs].res=1;t[rs].ma=t[rs].val=t[p].val;    }}void build(int l,int r,int p){    t[p].l=l;t[p].r=r;    t[p].res=0;    if (l==r) {t[p].ma=t[p].val=val[l];return;}    int m=(l+r)>>1;    build(l,m,ls);    build(m+1,r,rs);    t[p].ma=max(t[ls].ma,t[rs].ma);}void update(int l,int r,int val,int p){    if (t[p].l==l&&t[p].r==r) {        t[p].res=1;        t[p].val=t[p].ma=val;        return;    }    lazy(p);    int m=(t[p].l+t[p].r)>>1;    if (r<=m) update(l,r,val,ls);    else if (l>m) update(l,r,val,rs);    else {        update(l,m,val,ls);        update(m+1,r,val,rs);    }    t[p].ma=max(t[ls].ma,t[rs].ma);}void fk(int l,int r,int val,int p){    if (t[p].l==t[p].r){        t[p].res=0;        if (t[p].val>val) t[p].val=__gcd(val,t[p].val);        return;    }    if (t[p].l==l&&t[p].r==r&&t[p].res==1) {        if (t[p].val>=val) {            t[p].val=__gcd(val,t[p].val);        }        return;    }    lazy(p);    int m=(t[p].l+t[p].r)>>1;    if (t[p].l==l&&t[p].r==r){        if (t[p].ma<=val) ;        else{            fk(l,m,val,ls);            fk(m+1,r,val,rs);            t[p].ma=max(t[ls].ma,t[rs].ma);        }        return;    }    lazy(p);    if (r<=m) fk(l,r,val,ls);    else if (l>m) fk(l,r,val,rs);    else {        fk(l,m,val,ls);        fk(m+1,r,val,rs);    }    t[p].ma=max(t[ls].ma,t[rs].ma);}int query(int pos,int p){    if (t[p].l==t[p].r){        return t[p].val;    }    lazy(p);    int m=(t[p].l+t[p].r)>>1;    if (pos<=m) return query(pos,ls);    else return query(pos,rs);}int main(){    //freopen("1006in.txt","r",stdin);    int i,a,b,c,d,q;    scanf("%d",&cas);    while(cas--){        scanf("%d",&n);        for(i=1;i<=n;++i){            scanf("%d",&val[i]);        }        build(1,n,1);        scanf("%d",&q);        for(i=1;i<=q;++i){            scanf("%d%d%d%d",&a,&b,&c,&d);            if (a==1){                update(b,c,d,1);            }            else if (a==2){                fk(b,c,d,1);            }        }        for(i=1;i<=n;++i){            printf("%d ",query(i,1));        }        printf("\n");    }    return 0;}

 

1010,hdu4906:状态压缩DP

用dp[i][state]表示到第i个数能凑出state中有1的位的数的子序列有多少个。O(2^20*20*20)的复杂度。被卡掉。后来赵老师提醒0的状态很多,特判可以节省不少时间,再交,还是被卡。最后把memset改成for赋初值才通过。其实可以不用滚动数组,小的状态只会影响大的状态,状态从大到小dp就可以了。哎,真傻。

#include<iostream>#include<cstdio>#include<cstring>using namespace std;int mod=1000000007;int dp[2][2200000];int n,k,l;int cas,tn,kl,tmpk,now,pas,i,j,o;long long ns;int intns;int ans;int main(){    //freopen("1010in.txt","r",stdin);    scanf("%d",&cas);    tn=0;    while(cas--){        scanf("%d%d%d",&n,&k,&l);        memset(dp,0,sizeof(dp));        tn=(1<<k);        kl=min(k,l);        tmpk=1;        if (l>k) tmpk+=l-k;        now=0;        dp[0][0]=1;        for(i=1;i<=n;++i){            pas=now;now=1-now;            for(j=0;j<tn;++j) dp[now][j]=0;            //memset(dp[now],0,sizeof(dp[now]));            for(j=0;j<tn;++j){                if (dp[pas][j]==0) continue;                dp[now][j]=(dp[now][j]+((long long)dp[pas][j]*tmpk%mod))%mod;                for(o=1;o<=kl;++o){                    ns=(long long)j;                    ns=j|(ns<<(long long)o)|(1ll<<(o-1));                    intns=(int)(ns%tn);                    dp[now][intns]=(dp[now][intns]+dp[pas][j])%mod;                }            }        }        ans=0;        for(i=0;i<tn;++i){            if ((1<<(k-1))&i) ans=(ans+dp[now][i])%mod;        }        printf("%d\n",ans);    }    return 0;}



0 0
原创粉丝点击