SCOI 2016 bzoj 4567~4572 题解

bzoj 4567 [Scoi2016]背单词
首先,我们发现如果有S(a)是S(b)的后缀,那么S(a)一定先加入
那么倒着建字典树,每次dfs自己所有的儿子,看哪棵子儿子结束节点最多,按照这个顺序贪心
儿子结束节点:遍历当前节点子树能够到达并且不经过其他结束节点的节点
什么意思呢,假设说我们有一个aabb 有一个abb,那么我计算abb的时候可以忽略aabb的贡献,这两个串对于 b 只有1的贡献
而abb和aab对于b则有2的贡献
还有一种做法,正着建字典树然后AC自动机fail转移,比这个麻烦就不赘述了
时间 n
//Copyright(c)2016 liuchenrui
//Scoi 2016
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<ctime>
#include<iostream>
#define LL long long
using namespace std;
char s[510010];
int t[510010][26],cnt;
int f[510010],n,q[510000];
int *tmp[510010],tin[510010];
LL ans;int tot;int p[510010];
struct node{
    int id,v;
    friend bool operator < (const node &a,const node &b){
        return a.v<b.v;
    }
}ss[510010];int sum;
void dfs2(int now){
    if(f[now]){
        ss[++sum]=(node){now,q[now]};
        return;
    }
    for(int i=0;i<26;i++){
        if(t[now][i]){
            dfs2(t[now][i]);
        }
    }
}
void dfs(int now,int pre){
    ans+=tot+1-pre;
    tot++;p[now]=tot;
    sum=0;
    for(int i=0;i<25;i++){
        if(t[now][i]){
            dfs2(t[now][i]);
        }
    }
    sort(ss+1,ss+sum+1);
    tin[now]=sum;tmp[now]=new int[sum+2];
    for(int i=1;i<=tin[now];i++){
        tmp[now][i]=ss[i].id;
    }
    for(int i=1;i<=tin[now];i++){
        dfs(tmp[now][i],p[now]);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",s+1);
        int len=strlen(s+1),now=0;
        for(int l=1,r=len;l<r;l++,r--)swap(s[l],s[r]);
        for(int i=1;i<=len;i++){
            if(!t[now][s[i]-'a'])t[now][s[i]-'a']=++cnt;
            now=t[now][s[i]-'a'];q[now]++;
        }
        f[now]++;
    }
    int now=0;
    for(int i=0;i<25;i++){
        if(t[now][i]){
            dfs2(t[now][i]);
        }
    }
    sort(ss+1,ss+sum+1);
    tin[now]=sum;tmp[now]=new int[sum+2];
    for(int i=1;i<=tin[now];i++){
        tmp[now][i]=ss[i].id;
    }
    for(int i=1;i<=tin[now];i++){
        dfs(tmp[now][i],p[now]);
    }
    cout<<ans<<endl;
}



bzoj 4568 [Scoi2016]幸运数字
首先考虑单次询问:
http://www.lydsy.com/JudgeOnline/problem.php?id=4269
上面那个是这道题的子问题
然后我们发现上面那个线性基是可以合并的
1:链剖+线段树+线性基 nlog^2nlog^2w
2.倍增+st表+线性基 nlognlog^2w
3.点分+bfs+线性基 nlognlogw
这里2,3都能过,3会快一些
1的做法显然,常数好一点能70分
2的做法:维护t[i][j][]表示i节点向根2^j个节点的基
每次询问合并4个基即可(像st表那样)

3的做法:每层处理经过当前处理点的询问,每次bfs一遍,然后带着基合并下一个节点的基,每次询问暴力合并两个bfs到的节点的基

这里贴2的代码

//Copyright(c)2016 liuchenrui
#include<bits/stdc++.h>
#define LL long long
using namespace std;
template <typename T>
inline void splay(T &v){
    v=0;char c=0;
    while(c<'0' || c>'9')c=getchar();
    while(c>='0' && c<='9'){v=(v<<3)+(v<<1)+c-'0';c=getchar();}
}
struct Edge{
    int to,next;
}edge[40010];
int first[20010],size;
void addedge(int x,int y){
    size++;
    edge[size].to=y;
    edge[size].next=first[x];
    first[x]=size;
}
LL ji[20010][16][61];
int t[20010][16],deep[20010];
int fa[20010][16];
int swam(int x,int f){
    for(int i=15;i>=0;i--){
        if(1<<i<=f){
            f-=1<<i;
            x=fa[x][i];
        }
    }
    return x;
}
int lca(int x,int y){
    if(deep[x]<deep[y])swap(x,y);
    int f=deep[x]-deep[y];
    x=swam(x,f);
    if(x==y)return x;
    for(int i=15;i>=0;i--){
        if(fa[x][i]!=fa[y][i]){
            x=fa[x][i],y=fa[y][i];
        }
    }
    return fa[x][0];
}
LL s[40005],ans1[40005],ans2[40005],ans[40005];
int at1,at2,at;
int uni(LL *a,int &ta,LL *b,int tb,LL *c,int tc){
    int cnt=0;
    for(int i=1;i<=tb;i++){
        s[++cnt]=b[i];
    }
    for(int i=1;i<=tc;i++){
        s[++cnt]=c[i];
    }
    int f=0;
    for(LL j=1LL<<60;j;j>>=1){
        bool flag=false;
        for(int i=f+1;i<=cnt;i++){
            if(s[i]&j){
                swap(s[i],s[++f]);
                flag=true;break;
            }
        }
        if(!flag)continue;
        for(int i=1;i<=cnt;i++){
            if(i==f)continue;
            if(s[i]&j)s[i]^=s[f];
        }
    }
    ta=f;
    for(int i=1;i<=f;i++){
        a[i]=s[i];
    }
}
void dfs(int now,int F){
    fa[now][0]=F;deep[now]=deep[F]+1;
    for(int i=1;i<=15;i++){
        fa[now][i]=fa[fa[now][i-1]][i-1];
    }
    for(int i=1;i<=15;i++){
        uni(ji[now][i],t[now][i],ji[now][i-1],t[now][i-1],ji[fa[now][i-1]][i-1],t[fa[now][i-1]][i-1]);
    }
    for(int u=first[now];u;u=edge[u].next){
        if(edge[u].to!=F){
            dfs(edge[u].to,now);
        }
    }
}
int Log[20010],n,q;
int main(){
    splay(n),splay(q);
    for(int i=1;i<=n;i++){
        splay(ji[i][0][++t[i][0]]);
    }
    for(int i=1;i<n;i++){
        int x,y;splay(x),splay(y);
        addedge(x,y),addedge(y,x);
    }
    dfs(1,0);
    int cnt=0;
    for(int i=1;i<=n;i++){
        if(1<<cnt==i)cnt++;
        Log[i]=cnt-1;
    }
    for(;q--;){
        int x,y;splay(x),splay(y);
        int lc=lca(x,y);
        int s=deep[x]-deep[lc]+1;
        int tmp=swam(x,s-(1<<Log[s]));
        uni(ans1,at1,ji[x][Log[s]],t[x][Log[s]],ji[tmp][Log[s]],t[tmp][Log[s]]);
        s=deep[y]-deep[lc]+1;
        tmp=swam(y,s-(1<<Log[s]));
        uni(ans2,at2,ji[y][Log[s]],t[y][Log[s]],ji[tmp][Log[s]],t[tmp][Log[s]]);
        uni(ans,at,ans1,at1,ans2,at2);
        LL Ans=0;
        for(int i=1;i<=at;i++){
            Ans^=ans[i];
        }
        printf("%lld\n",Ans);
    }
}


update:更新了更快的代码 2016.05.17

//Copyright(c)2016 liuchenrui
#include<bits/stdc++.h>
#define LL long long
using namespace std;
template<typename T>
inline void splay(T&v){
	v=0;char c=0;
	while(c<'0'||c>'9')c=getchar();
	while(c>='0'&&c<='9'){v=(v<<3)+(v<<1)+c-'0';c=getchar();}
}
struct Edge{
	int to,next;
}
edge[40010];
int first[20010],size,mxs;
void addedge(int x,int y){
	size++;
	edge[size].to=y;
	edge[size].next=first[x];
	first[x]=size;
}
LL *ji[20010][16];
int t[20010][16],deep[20010];
int fa[20010][16];
int swam(int x,int f){
	for(int i=15;i>=0;i--){
		if(1<<i<=f){
			f-=1<<i;
			x=fa[x][i];
		}
	}
	return x;
}
int lca(int x,int y){
	if(deep[x]<deep[y])swap(x,y);
	int f=deep[x]-deep[y];
	x=swam(x,f);
	if(x==y)return x;
	for(int i=15;i>=0;i--){
		if(fa[x][i]!=fa[y][i]){
			x=fa[x][i],y=fa[y][i];
		}
	}
	return fa[x][0];
}
LL s[40005],ans[40005];
int at1,at2,at;
int uni2(LL*a,int&ta,LL*b,int tb){
	int cnt=0;
	for(int i=1;i<=tb;i++){
		a[++ta]=b[i];
	}
}
int uni(LL*&a,int&ta,LL*b,int tb,LL*c,int tc){
	int cnt=0;
	for(int i=1;i<=tb;i++){
		s[++cnt]=b[i];
	}
	for(int i=1;i<=tc;i++){
		s[++cnt]=c[i];
	}
	int f=0;
	for(LL j=1LL<<mxs;j;j>>=1){
		bool flag=false;
		for(int i=f+1;i<=cnt;i++){
			if(s[i]&j){
				swap(s[i],s[++f]);
				flag=true;
				break;
			}
		}
		if(!flag)continue;
		for(int i=1;i<=cnt;i++){
			if(i==f)continue;
			if(s[i]&j)s[i]^=s[f];
		}
	}
	a=new LL[f+2];ta=f;
	for(int i=1;i<=f;i++){
		a[i]=s[i];
	}
}
int calc(LL*a,int&ta){
	int f=0;
	for(LL j=1LL<<mxs;j;j>>=1){
		bool flag=false;
		for(int i=f+1;i<=ta;i++){
			if(a[i]&j){
				swap(a[i],a[++f]);
				flag=true;
				break;
			}
		}
		if(!flag)continue;
		for(int i=1;i<=ta;i++){
			if(i==f)continue;
			if(a[i]&j)a[i]^=a[f];
		}
	}
	ta=f;
}
void dfs(int now,int F){
	fa[now][0]=F;
	deep[now]=deep[F]+1;
	for(int i=1;i<=15;i++){
		fa[now][i]=fa[fa[now][i-1]][i-1];
	}
	for(int i=1;i<=15;i++){
		if(fa[now][i-1]){
			uni(ji[now][i],t[now][i],ji[now][i-1],t[now][i-1],ji[fa[now][i-1]][i-1],t[fa[now][i-1]][i-1]);
		}
	}
	for(int u=first[now];u;u=edge[u].next){
		if(edge[u].to!=F){
			dfs(edge[u].to,now);
		}
	}
}
int Log[20010],n,q;
int main(){
	freopen("xxx.in","r",stdin);
	splay(n),splay(q);
	for(int i=1;i<=n;i++){
		LL x;splay(x);ji[i][0]=new LL[2];
		ji[i][0][++t[i][0]]=x;
		while(1LL<<mxs<=x)mxs++;
	}
	mxs++;if(mxs>60)mxs=60;
	for(int i=1;i<n;i++){
		int x,y;
		splay(x),splay(y);
		addedge(x,y),addedge(y,x);
	}
	dfs(1,0);
	int cnt=0;
	for(int i=1;i<=n;i++){
		if(1<<cnt==i)cnt++;
		Log[i]=cnt-1;
	}
	for(;q--;){
		int x,y;at=0;
		splay(x),splay(y);
		int lc=lca(x,y);
		int s=deep[x]-deep[lc]+1;
		int tmp=swam(x,s-(1<<Log[s]));
		uni2(ans,at,ji[x][Log[s]],t[x][Log[s]]);
		uni2(ans,at,ji[tmp][Log[s]],t[tmp][Log[s]]);
		s=deep[y]-deep[lc]+1;
		tmp=swam(y,s-(1<<Log[s]));
		uni2(ans,at,ji[y][Log[s]],t[y][Log[s]]);
		uni2(ans,at,ji[tmp][Log[s]],t[tmp][Log[s]]);
		calc(ans,at);
		LL Ans=0;
		for(int i=1;i<=at;i++){
			Ans^=ans[i];
		}
		printf("%lld\n",Ans);
	}
}



bzoj4569 [Scoi2016]萌萌哒
维护f[i][j]表示a[i],a[i+1]..a[i+(2^j)-1]与谁合并了
每次合并可以拆成log段
合并完成过后发现:
如果f[i][j]=k 相当于 f[i][j-1]=k && f[i][i+(2^j)-1]=k+(2^j)-1
于是可以下放所有合并
最后统计答案就简单了:9*(10^联通块个数-1)
时间 nlogn
//Copyright(c)2016 liuchenrui
//Scoi 2016
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<ctime>
#include<iostream>
#define LL long long
#define mod 1000000007
using namespace std;
void read(int &v){
    v=0;char c='a';bool p=0;
    for(c=getchar();c>'9'||c<'0';c=getchar()){
        if(c=='-')p=1;
    }
    for(;c>='0'&&c<='9';c=getchar()){
        v=v*10+c-'0';
    }
    if(p)v=-v;
}
int a,A,b,B,n,m;
int getnum(int i,int j){
    int x=(i-1)*20+j;
    if(x>=n*20)x=n*20;
    return x;
}
int f[100010][20];
int fa[2500010];
int getfa(int v){
    if(fa[v]==v)return v;
    else return fa[v]=getfa(fa[v]);
}
void uni(int x,int y){
    //cerr<<x<<" "<<y<<endl;
    x=getfa(x),y=getfa(y);
    if(x>y)swap(x,y);
    fa[y]=x;
}
void cal(int v){
    int x=v/20+1,y=v%20;
    a=getnum(x,y-1);
    b=getnum(x+(1<<(y-1)),y-1);
    return;
}
void cal2(int v){
    int x=v/20+1,y=v%20;
    A=getnum(x,y-1);
    B=getnum(x+(1<<(y-1)),y-1);
    return;
}
int main(){
    read(n),read(m);
    for(int i=1;i<=2000001;i++)fa[i]=i;
    for(int i=1;i<=m;i++){
        int l,r,L,R;
        read(l),read(r),read(L),read(R);
        for(int j=19;j>=0;j--){
            if(l+(1<<j)-1<=r){
                uni(getnum(l,j),getnum(L,j));
                l=l+(1<<j);L=L+(1<<j);
            }
        }
    }
    for(int j=19;j>=1;j--){
        for(int i=1;i<=n;i++){
            int x=getfa(getnum(i,j));
            cal(x);cal2(getnum(i,j));
            uni(a,A),uni(b,B);
        }
    }
    int ans=1;
    for(int i=1;i<=n;i++){
        if(getfa(getnum(i,0))==getnum(i,0)){
            if(i==1)ans=(LL)ans*9%mod;
            else ans=(LL)ans*10%mod;
        }
    }
    cout<<ans<<endl;
}



bzoj 4570 [Scoi2016]妖怪
首先强烈谴责出题人卡sort这种行为
我们发现,把每只怪物看做坐标上的点,问题转化为:求一个斜率,过所有点都做这个斜率的直线,会得到很多坐标截距,求最小的x截距+y截距
肯定求右上凸壳,然后用相邻两个点或者自己更新答案
相邻两个点就是这两个点组成的斜率
自己更新答案:设当前点(x,y),那么最小截距为x+y+2*sqrt(xy)
注意如果当前斜率不能掩盖旁边点的时候不能更新答案!
时间 nlogn
顺便说一句,求叉积直接求比构造函数快很多
垃圾出题人,卡什么常数
//Copyright(c)2016 liuchenrui
//Scoi 2016
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<ctime>
#include<cstdlib>
#include<cmath>
#define LL long long
#define dis(a,b) ((LL)(a.x-b.x)*(a.x-b.x)+(LL)(a.y-b.y)*(a.y-b.y))
using namespace std;
void read(int &v){
    char c='a';v=0;
    for(c=getchar();c>'9'||c<'0';c=getchar());
    for(;c<='9'&&c>='0';c=getchar()){
        v=v*10+c-'0';
    }
}
struct P{
    int x,y;
    friend P operator - (const P &a,const P &b){
        return (P){a.x-b.x,a.y-b.y};
    }
    friend LL operator * (const P &a,const P &b){
        return (LL)a.x*b.y-(LL)a.y*b.x;
    }
}p[1000010],s[1000010];
#define c p[1]
bool operator < (const P &a,const P &b){
    LL t=(LL)(a.x-c.x)*(a.y-c.y)-(LL)(a.y-c.y)*(b.x-c.x);
    if(t==0)return dis(a,c)<dis(b,c);
    return t>0;
}
double ans=1e18;
int n;
double inter(const P &a,const P &b){
    if(a.x==b.x)return 1e18;
    double x=a.x-b.x,y=b.y-a.y;
    double x1=b.x,y2=a.y;
    double y1=x1/x*y,x2=y2/y*x;
    return x1+x2+x+y1+y2+y;
}
double checknum(P x){
    return (double)x.x+(double)x.y+sqrt((double)x.x*x.y)*2;
}
double slop(P x,P y){
    if(x.x-y.x==0)return -1e18;
    return (double)(x.y-y.y)/(x.x-y.x);
}
double slop(P x){
    return -((double)x.y+sqrt((double)x.x*x.y))/((double)x.x+sqrt((double)x.x*x.y));
}
int main(){
    read(n);
    for(int i=1;i<=n;i++){
        read(p[i].x),read(p[i].y);
    }
    int t=1;
    for(int i=2;i<=n;i++){
        if(p[i].y<p[t].y)t=i;
        if(p[i].y==p[t].y&&p[i].x<p[t].x)t=i;
    }
    swap(p[1],p[t]);
    sort(p+2,p+n+1);int top=0;
    s[++top]=p[1],s[++top]=p[2];
    for(int i=3;i<=n;i++){
        while((s[top]-s[top-1])*(p[i]-s[top-1])<0)top--;
        s[++top]=p[i];
    }
    int l=1,r=1;
    for(int i=2;i<=top;i++){
        if(s[i].x>s[l].x)l=i;
        if(s[i].x==s[l].x&&s[i].y<s[l].y)l=i;
        if(s[i].y>s[r].y)r=i;
        if(s[i].y==s[r].y&&s[i].x<s[r].x)r=i;
    }
    for(int i=l;i<r;i++){
        double x=inter(s[i],s[i+1]);
        if(x<ans)ans=x;
    }
    for(int i=l+1;i<r;i++){
        double x,y,z;
        x=slop(s[i],s[i+1]);
        y=slop(s[i-1],s[i]);
        z=slop(s[i]);
        if((z>=y)&&(z<=x)){
            ans=min(ans,checknum(s[i]));
        }
    }
    if(r-l>=1){
        double x,y,z;
        x=slop(s[l],s[l+1]);
        z=slop(s[l]);
        if(z<=x){
            ans=min(ans,checknum(s[l]));
        }
        y=slop(s[r-1],s[r]);
        z=slop(s[r]);
        if(z>=y){
            ans=min(ans,checknum(s[r]));
        }
    }
    printf("%.4f",ans);
}



bzoj 4571 [Scoi2016]美味
b ^ (a[i] + x)
考虑从高位向低位贪心,每次看这位答案能不能为1
能为1当且仅当当前询问区间内有数减去x后这一位^b的这一位为1,并且高位满足上几次贪心的结果
我们发现,如果一个数确定了高位,低位没有确定时,取值范围是一段连续的区间,减去x后也是
那么建可持久化权值线段树,每次询问枚举当前位,在权值线段树中查询能否把这位答案设为1
时间 nlog^2w

//Copyright(c)2016 liuchenrui
//Scoi 2016
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<ctime>
#include<cstdlib>
#define P 233
#define M 1000000
using namespace std;
void read(int &v){
    char c='a';v=0;bool p=0;
    for(c=getchar();c>'9'||c<'0';c=getchar()){
        if(c=='-')p=1;
    }
    for(;c<='9'&&c>='0';c=getchar()){
        v=v*10+c-'0';
    }
    if(p)v=-v;
}
int n,m;
int a[200010];
int ls[8000000],rs[8000000],val[8000000],tot;
int s[101],root[200010];
void insert(int pre,int &now,int l,int r,int pos){
    if(!now)now=++tot;
    if(l==r){
        val[now]=val[pre]+1;
        return;
    }
    int mid=l+r>>1;
    if(pos<=mid)insert(ls[pre],ls[now],l,mid,pos),rs[now]=rs[pre];
    else insert(rs[pre],rs[now],mid+1,r,pos),ls[now]=ls[pre];
    val[now]=val[ls[now]]+val[rs[now]];
}
int ishave(int pre,int now,int l,int r,int L,int R){
    if(!(val[now]-val[pre]))return 0;
    if(L<=l && r<=R){
        return 1;
    }
    int mid=l+r>>1,ret=0;
    if(L<=mid)ret|=ishave(ls[pre],ls[now],l,mid,L,R);
    if(R>=mid+1)ret|=ishave(rs[pre],rs[now],mid+1,r,L,R);
    return ret;
}
int main(){
    read(n),read(m);
    for(int i=1;i<=n;i++){
        read(a[i]);
        insert(root[i-1],root[i],0,M,a[i]);
    }
    for(;m--;){
        int b,x,l,r;
        read(b),read(x),read(l),read(r);
        for(int i=30,t=b;i>=1;i--,t>>=1)s[i]=t&1;
        int L=0,R=(1<<30)-1;
        for(int i=1;i<=30;i++){
            int mid=L+R>>1;
            if(s[i]){
                if(ishave(root[l-1],root[r],0,M,L-x,mid-x))R=mid;
                else L=mid+1;
            }
            else{
                if(ishave(root[l-1],root[r],0,M,mid+1-x,R-x))L=mid+1;
                else R=mid;
            }
        }
        printf("%d\n",b^L);
    }
}





bzoj 4572 [Scoi2016]围棋
http://blog.csdn.net/jzhang1/article/details/51276734
还有容斥做法(我不会
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值