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

多校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
#include
#include
using namespace std;
#define ls (p<<1)
#define rs (p<<1|1)
#define NN 201000


int 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[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[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[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

 

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

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

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

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

#include
#include
#include
#include
#include
using namespace std;
#define NN 4100

char 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) 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;
		else
			nxt[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>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=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;in) 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
#include
#include
#include
using namespace std;
#define ls (p<<1)
#define rs (p<<1|1)
#define NN 101000

int 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
#include
#include
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) tmpk+=l-k;
        now=0;
        dp[0][0]=1;
        for(i=1;i<=n;++i){
            pas=now;now=1-now;
            for(j=0;j



你可能感兴趣的:(2014hdu多校,HDU)