【题解】毒瘤 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】
不知道这儿能放啥,干脆买个萌吧(⊙ω⊙)