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

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


【Day1 T1】背单词

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

【题目描述】

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

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

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

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

【分析】

这题极其诡异,小菜鸡很难讲清楚,干脆挂一下 \(\text{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\n",ans);
    fclose(stdin);
    fclose(stdout);
}

【Day1 T2】幸运数字

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

【题目描述】

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

【分析】

暴力倍增线性基合并。

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

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

时间复杂度:\(O(n\log^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\n",T1.ask(x,y).ask());
}

【Day1 T3】萌萌哒

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

【题目描述】

你需要构造一个长为 \(n\) \((n\leqslant 10^5)\) 的数 \(S\),给出 \(m\) \((m\leqslant 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(n\log 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\n",ans);
}

【Day2 T1】妖怪

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

【题目描述】

给出 \(n\) \((n\leqslant 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_{i\in[1,n]}(a,b)\}\) 最小,输出这个最小值。

【分析】

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

【Code】

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

【Day2 T2】美味

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

【题目描述】

给出一个长为 \(n\) \((n\leqslant 2*10^5)\) 的序列 \(a\) \((0\leqslant a_i< 10^5)\),有 \(m\) \((m\leqslant 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-x\leqslant a_i \leqslant (now|(1<<i))-x-1\),要想为 \(0\) 必须满足存在一个 \(i\) 使得 \((now|(1<<i))-x\leqslant a_i\leqslant (now|(1<<i+1))-x-1\) 。建一颗主席树,每次查询 \([l,r]\) 内满足要求的 \(i\) 的个数即可。

时间复杂度:\(O(n\log \inf+m\log^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\n",now^b);
    }
}

【Day2 T3】围棋

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

【题目描述】

略。

【分析】

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

【Code】

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

posted @ 2020-04-23 20:39  辰星凌  阅读(372)  评论(0编辑  收藏  举报