【题解】毒瘤 OI 刷题汇总 [SCOI2016]

【题解】毒瘤 OI 刷题汇总 [SCOI2016]


【Day1 T1】背单词

传送门:背单词 ( ext{[P3294]}) ( ext{[Bzoj4567]})

【题目描述】

给你一些字符串,让你对其进行排列,要求满足以下规则,求最少花费
(x) 为字符串在自行排定的序列中的位置,当前字符串为 (a))

  • 如果 (a) 存在后缀且 (a) 的后缀在 (a) 之后,则花费 (n^2)

  • 如果 (a) 不存在后缀则花费 (x)

  • (a) 存在后缀且 (a) 的后缀在 (a) 之前,设 (y)(a) 之前离其最近的是 (a) 的后缀的字符串的位置,则花费 (x−y)

【分析】

这题极其诡异,小菜鸡很难讲清楚,干脆挂一下 ( ext{Tian_Xing}) 神仙的博客链接好了 【这是一个链接】

【Code】

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<vector>
#include<map>
#define LL long long
#define Re register int
#define IT vector<int>::iterator
using namespace std;
const int N=1e5+5,M=510003;
int n,o;LL ans;char ch[M];map<string,int>ID;
vector<int>to[N];
inline void add(Re x,Re y){to[x].push_back(y);}
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
int O=1,now,id[N],fa[M*26],ed[M*26],size[N],tr[M*26][26];
inline int find(Re x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void insert(char ch[],Re n,Re id){
    Re p=1;
    for(Re i=1;i<=n;++i){
        Re a=ch[i]-'a';
        if(!tr[p][a])tr[p][a]=++O;
        p=tr[p][a];
    }
    ed[p]=id+1;
}
inline bool cmp(Re x,Re y){return size[x]<size[y];}
inline void build(Re x){
    for(Re i=0;i<26;++i)
        if(tr[x][i]){
            if(ed[tr[x][i]])add(ed[find(x)],ed[fa[tr[x][i]]=tr[x][i]]);
            else fa[tr[x][i]]=find(x);
            build(tr[x][i]);
        }
}
inline void dfs(Re x){
    size[x]=1;
    for(IT i=to[x].begin();i!=to[x].end();++i)dfs(*i),size[x]+=size[*i];
    sort(to[x].begin(),to[x].end(),cmp);
}
inline void dfs_(Re x,Re fa){
    ans+=(id[x]-fa);
    for(IT i=to[x].begin();i!=to[x].end();++i)id[*i]=++now,dfs_(*i,id[x]);
}
int main(){
//    freopen("word.in","r",stdin);
//    freopen("word.out","w",stdout);
    in(n);
    for(Re i=1;i<=n;++i){
        scanf("%s",ch+1);Re m=strlen(ch+1);
        for(Re j=1;j<=m/2;++j)swap(ch[j],ch[m-j+1]);
        insert(ch,m,i);
    }
    ed[fa[1]=1]=1,build(1),dfs(1),dfs_(1,0);
    printf("%lld
",ans);
    fclose(stdin);
    fclose(stdout);
}

【Day1 T2】幸运数字

传送门:幸运数字 ( ext{[P3292]}) ( ext{[Bzoj4568]})

【题目描述】

给出一颗 (n) ((nleqslant 2*10^4)) 个点的树,多次询问树上两点之间的路径上最大点权异或和。

【分析】

暴力倍增线性基合并。

考虑在倍增求 (LCA) 时同时维护线性基,即用 (f[x][j]) 储存点 (x) 到它的 (2^j) 级祖先处的路径上的所有点所组成的线性基信息。

对于每次询问,在往上跳跃时顺便把路径上的各个线性基合并起来,最后查询最大值即可。

时间复杂度:(O(nlog^3 n))

(把倍增换成树剖貌似常数要小一些)

【Code】

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#define LL long long
#define Re register LL
using namespace std;
const LL N=2e4+3,M=2e5+3,inf=2e18,logN=14;
LL n,m,o,x,y,T,A[N],head[N];
struct QAQ{LL to,next;}a[N<<1];
inline void add(Re x,Re y){a[++o].to=y,a[o].next=head[x],head[x]=o;}
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
struct JI{
    LL p[61];JI(){memset(p,0,sizeof(p));}
    inline void insert(Re x){
        for(Re i=60;i>=0;--i)
            if((x>>i)&1){
                if(!p[i]){p[i]=x;break;}
                x^=p[i];
            }
    }
    inline void merge(JI O){
        for(Re i=60;i>=0;--i)if(O.p[i])insert(O.p[i]);
    }
    inline LL ask(){
        Re ans=0;
        for(Re i=60;i>=0;--i)ans=max(ans,ans^p[i]);
        return ans;
    }
};
struct LCA{
    LL deep[N],fa[N][15],ant[N][15];JI dp[N][15];
    inline void dfs(Re x,Re fa){
        deep[x]=deep[ant[x][0]=fa]+1,dp[x][0].insert(A[fa]);
        for(Re j=1;(1<<j)<=deep[x];++j)ant[x][j]=ant[ant[x][j-1]][j-1],dp[x][j]=dp[x][j-1],dp[x][j].merge(dp[ant[x][j-1]][j-1]);
        for(Re i=head[x];i;i=a[i].next)if(a[i].to!=fa)dfs(a[i].to,x);
    }
    inline JI ask(Re x,Re y){
        JI Ans;Ans.insert(A[x]),Ans.insert(A[y]);
        if(deep[x]<deep[y])swap(x,y);
        for(Re i=logN;i>=0;--i)if(deep[ant[x][i]]>=deep[y])Ans.merge(dp[x][i]),x=ant[x][i];
        if(x==y)return Ans;
        for(Re i=logN;i>=0;--i)if(ant[x][i]!=ant[y][i])Ans.merge(dp[x][i]),Ans.merge(dp[y][i]),x=ant[x][i],y=ant[y][i];
        Ans.merge(dp[x][0]);
        return Ans;
    }
}T1;
int main(){
//    freopen("lucky.in","r",stdin);
//    freopen("lucky.out","w",stdout);
    in(n),in(T),m=n-1;
    for(Re i=1;i<=n;++i)in(A[i]);
    while(m--)in(x),in(y),add(x,y),add(y,x);
    T1.dfs(1,0);
    while(T--)in(x),in(y),printf("%lld
",T1.ask(x,y).ask());
}

【Day1 T3】萌萌哒

传送门:萌萌哒 ( ext{[P3295]}) ( ext{[Bzoj4569]})

【题目描述】

你需要构造一个长为 (n) ((nleqslant 10^5)) 的数 (S),给出 (m) ((mleqslant 10^5)) 个限制条件,每个限制条件包含四个整数 (l_1,r_1,l_2,r_2),表示 (S_{l_1}S_{l_1+1}...S_{r_1})(S_{l_2}S_{l_2+1}...S_{r_2}) 必须完全相同。求合法 (S) 的个数。

【分析】

神奇的倍增优化冰茶姬。

有一个很 (naive) 的想法:冰茶姬暴力枚举连边,最后求出不同联通块的个数 (cnt),则答案为 (9*10^{cnt})

考虑用倍增进行优化,用 (fa[x][j]) 表示区间 ([x,x+2^j-1]) 与其他点的关系,对于每个限制条件把给出区间分为 (log) 部分依次连边,最后再依次根据长为 (2^j) 的大区间连通块信息 对长度为 (2^{j-1}) 的小区间进行合并。有点类似于打懒标记和最后标记的下放(或者说和线段树优化建边比较像?)。

时间复杂度:(O(nlog n))(带一个冰茶姬的常数)。

【Code】

#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=1e5+3,logN=17,P=1e9+7;
int n,a,b,c,d,T,cnt=-1,fa[N][20];LL ans=9;
inline void in(Re &x){
    Re f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline int find(Re x,Re k){return fa[x][k]==x?x:fa[x][k]=find(fa[x][k],k);}
inline void merge(Re x,Re y,Re k){if((x=find(x,k))!=(y=find(y,k)))fa[x][k]=y;}
int main(){
//    freopen("123.txt","r",stdin);
    in(n),in(T);
    for(Re j=0;j<=logN;++j)
        for(Re i=1;i<=n;++i)
            fa[i][j]=i;
    while(T--){
        in(a),in(b),in(c),in(d);
        for(Re j=logN;j>=0;--j)if(a+(1<<j)-1<=b)merge(a,c,j),a+=(1<<j),c+=(1<<j);
    }
    for(Re j=logN;j>=1;--j)
        for(Re i=1;i+(1<<j)-1<=n;++i)
            merge(i,find(i,j),j-1),merge(i+(1<<j-1),find(i,j)+(1<<j-1),j-1);
    for(Re i=1;i<=n;++i)if(find(i,0)==i)++cnt;
    while(cnt--)(ans*=10)%=P;
    printf("%lld
",ans);
}

【Day2 T1】妖怪

传送门:妖怪 ( ext{[P3291]}) ( ext{[Bzoj4570]})

【题目描述】

给出 (n) ((nleqslant 10^6)) 个点 ((x_i,y_i)),设 (f_i(a,b)=x_i+y_i+frac{bx_i}{a}+frac{ay_i}{b}),求出一组 ((a,b)),使得 (max{f_{iin[1,n]}(a,b)}) 最小,输出这个最小值。

【分析】

【题解】妖怪 ( ext{[SCOI2016] [P3291] [Bzoj4570]})

【Code】

不知道这儿能放啥,干脆买个萌吧(⊙ω⊙)

【Day2 T2】美味

传送门:美味 ( ext{[P3293]}) ( ext{[Bzoj4571]})

【题目描述】

给出一个长为 (n) ((nleqslant 2*10^5)) 的序列 (a) ((0leqslant a_i< 10^5)),有 (m) ((mleqslant 10^5)) 次询问,每次询问给出四个整数 (b x l r),在区间 ([l,r]) 中寻找一个 (i) 使得 (b operatorname{xor} (a_i+x)) 最大,并输出这个这个最大值。

【分析】

暴力枚举+主席树瞎搞。

首先肯定是要从高位到低位依次枚举,每次贪心选对应位上与 (b) 不同的(异或起来尽量大)。设当前所选出的 (a_i+x) 只取前 (i-1) 位时等于 (now),则要想使得 (now) 的第 (i) 位为 (1) 就必须满足存在一个 (i) 使得 (now-xleqslant a_i leqslant (now|(1<<i))-x-1),要想为 (0) 必须满足存在一个 (i) 使得 ((now|(1<<i))-xleqslant a_ileqslant (now|(1<<i+1))-x-1) 。建一颗主席树,每次查询 ([l,r]) 内满足要求的 (i) 的个数即可。

时间复杂度:(O(nlog inf+mlog^2 inf))

【Code】

#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=2e5+5,M=1e5+3,logM=17;
int n,m,b,x,l,r,T,A[N];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
int pt[N];
struct Segment_Tree{
    #define pl (tr[p].lp)
    #define pr (tr[p].rp)
    #define mid ((L+R)>>1)
    int O;
    struct QAQ{int S,lp,rp;}tr[N*logM<<1];
    inline void creat(Re &p,Re q,Re L,Re R,Re x){
        tr[p=++O]=tr[q],++tr[p].S;
        if(L==R)return;
        if(x<=mid)creat(pl,tr[q].lp,L,mid,x);
        else creat(pr,tr[q].rp,mid+1,R,x);
    }
    inline int ask(Re p,Re q,Re L,Re R,Re l,Re r){
        if(!p&&!q)return 0;
        if(l<=L&&R<=r)return tr[p].S-tr[q].S;
        Re ans=0;
        if(l<=mid)ans+=ask(pl,tr[q].lp,L,mid,l,r);
        if(r>mid)ans+=ask(pr,tr[q].rp,mid+1,R,l,r);
        return ans;
    }
}TR;
int main(){
//    freopen("123.txt","r",stdin);
    in(n),in(T);
    for(Re i=1;i<=n;++i)in(A[i]),m=max(m,A[i]);
    for(Re i=1;i<=n;++i)TR.creat(pt[i],pt[i-1],0,m,A[i]);
    while(T--){
        in(b),in(x),in(l),in(r);Re now=0;
        for(Re i=logM;i>=0;--i)
            if((b>>i)&1){
                if(TR.ask(pt[r],pt[l-1],0,m,now-x,now+(1<<i)-x-1));//取o^1=0
                else now|=1<<i;//取o=1 
            }
            else{
                if(TR.ask(pt[r],pt[l-1],0,m,now+(1<<i)-x,now+(1<<i+1)-x-1))now|=1<<i;//取o^1=1 
                else;//取o=0 
            }
        printf("%d
",now^b);
    }
}

【Day2 T3】围棋

传送门:围棋 ( ext{[P3290]}) ( ext{[Bzoj4572]})

【题目描述】

略。

【分析】

又双叒叕是一道毒瘤插头 (dp),不会,咕掉。

【Code】

不知道这儿能放啥,干脆买个萌吧(⊙ω⊙)

原文地址:https://www.cnblogs.com/Xing-Ling/p/12763384.html