BJOI2018 简要题解

二进制
序列上线段树维护DDP好题。
题解可以看这篇
代码:

#include
#define ri register int
using namespace std;
const int N=1e5+5;
typedef long long ll;
int n;
bool a[N];
inline int read(){
	int ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
inline int add(const ll&a,const ll&b){return !a*b?0:a+b;}
namespace SGT{
	#define lc (p<<1)
	#define rc (p<<1|1)
	#define mid (T[p].l+T[p].r>>1)
	struct Node{int l,r,det;ll ans,c[2][2][2][2],d[2][2];}T[N<<2];
	inline Node operator+(const Node&a,const Node&b){
		Node ret;
		ret.l=a.l,ret.r=b.r,ret.det=a.det+b.det,ret.ans=a.ans+b.ans,memset(ret.c,0,sizeof(ret.c));
		ret.d[0][0]=a.d[0][0]+(a.det?0:b.d[0][0]);
		ret.d[0][1]=a.d[0][1]+(a.det<2?b.d[0][1-a.det]:0);
		ret.d[1][0]=b.d[1][0]+(b.det?0:a.d[1][0]);
		ret.d[1][1]=b.d[1][1]+(b.det<2?a.d[1][1-b.det]:0);
		for(ri i=0;i<2;++i)for(ri j=0;j<2;++j)for(ri k=0;k<2;++k)ret.c[0][i][j][k]=a.c[0][i][j][k],ret.c[1][i][j][k]=b.c[1][i][j][k];
		int tl=a.r-a.l+1-a.det,tr=b.r-b.l+1-b.det;
		for(ri k=0;k+tl<2;++k){
			ret.c[0][0][0][k+tl]+=a.det?0:b.c[0][0][0][k];
			ret.c[0][0][1][k+tl]+=b.c[0][a.det&1][1][k]+(a.det?b.c[0][a.det&1][0][k]:0);
			ret.c[0][1][0][k+tl]+=a.det<2?b.c[0][1-a.det][0][k]:0;
			ret.c[0][1][1][k+tl]+=b.c[0][(a.det&1)^1][1][k]+(a.det>1?b.c[0][(a.det&1)^1][0][k]:0);
		}
		for(ri k=0;k+tr<2;++k){
			ret.c[1][0][0][k+tr]+=b.det?0:a.c[1][0][0][k];
			ret.c[1][0][1][k+tr]+=a.c[1][b.det&1][1][k]+(b.det?a.c[1][b.det&1][0][k]:0);
			ret.c[1][1][0][k+tr]+=b.det<2?a.c[1][1-b.det][0][k]:0;
			ret.c[1][1][1][k+tr]+=a.c[1][(b.det&1)^1][1][k]+(b.det>1?a.c[1][(b.det&1)^1][0][k]:0);
		}
		for(ri i=0;i<2;++i)for(ri j=0;i+j<2;++j){
			ret.ans+=a.c[1][0][0][i]*b.c[0][1][1][j]+a.c[1][0][1][i]*b.c[0][1][1][j]+a.c[1][0][1][i]*b.c[0][1][0][j];
            ret.ans+=a.c[1][1][0][i]*b.c[0][0][1][j]+a.c[1][1][1][i]*b.c[0][0][1][j]+a.c[1][1][1][i]*b.c[0][0][0][j];
        }
		ret.ans+=a.d[1][0]*b.d[0][1]+a.d[1][1]*b.d[0][0];
		return ret;
	}
	inline void solve(int p){
		T[p].ans=T[p].det=0,memset(T[p].c,0,sizeof(T[p].c)),memset(T[p].d,0,sizeof(T[p].d));
		if(a[T[p].l])T[p].ans=T[p].det=T[p].c[0][1][0][0]=T[p].c[1][1][0][0]=T[p].d[0][1]=T[p].d[1][1]=1;
		else T[p].c[0][0][0][1]=T[p].c[1][0][0][1]=T[p].d[0][0]=T[p].d[1][0]=1;
	}
	inline void build(int p,int l,int r){
		T[p].l=l,T[p].r=r;
		if(l==r)return solve(p);
		build(lc,l,mid),build(rc,mid+1,r),T[p]=T[lc]+T[rc];
	}
	inline void update(int p,int k){
		if(T[p].l==T[p].r)return solve(p);
		update(k<=mid?lc:rc,k),T[p]=T[lc]+T[rc];
	}
	inline Node query(int p,int ql,int qr){
		if(ql<=T[p].l&&T[p].r<=qr)return T[p];
		if(qr<=mid)return query(lc,ql,qr);
		if(ql>mid)return query(rc,ql,qr);
		return query(lc,ql,mid)+query(rc,mid+1,qr);
	}
}
int main(){
	n=read();
	for(ri i=1;i<=n;++i)a[i]=read();
	SGT::build(1,1,n);
	for(ri tt=read(),op,l,r;tt;--tt){
		op=read();
		if(op==1)a[l=read()]^=1,SGT::update(1,l);
		else l=read(),r=read(),cout<<(ll)(r-l+1)*(r-l+2)/2-SGT::query(1,l,r).ans<<'\n';
	}
	return 0;
}

染色
结论神题。
首先奇环先判掉(这样已经能过非hack数据了
然后对于偶环的情况分类讨论。
对于一个连通块来说。

  1. n ≥ m n\ge m nm一定合法
  2. n + 2 ≤ m n+2\le m n+2m一定不合法
  3. n + 1 = m n+1=m n+1=m,这个时候看如果两个环没有套在一起一定不合法,否则我们把这个连通分量给拽出来,一定有两个点度数是 3 3 3其它点度数是 2 2 2,这两个关键点间有三条路径,如果有两条长度均为 2 2 2一定合法,否则一定不合法。

代码:

#include
#define ri register int
using namespace std;
inline int read(){
	int ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
const int N=10005;
vector<int>e[N],a[N];
int n,m,dfn[N],stk[N],low[N],du[N],sig=0,tot=0,top=0,res,ec,pc,ck=0;
bool vis[N],in[N],col[N];
bool dfs(int p,bool f){
	vis[p]=1,col[p]=f;
	for(ri i=0,v;i<e[p].size();++i){
		if(vis[v=e[p][i]]){if(col[p]^col[v])continue;return 0;}
		if(!dfs(v,f^1))return 0;
	}
	return 1;
}
void tarjan(int p){
	++pc,stk[++top]=p,dfn[p]=low[p]=++tot;
	for(ri i=0,v;i<e[p].size();++i){
		++ec;
		if(dfn[v=e[p][i]])low[p]=min(dfn[v],low[p]);
		else{
			tarjan(v);
			if(low[v]>=dfn[p]){
				a[++sig].clear();
				a[sig].push_back(p);
				int x;
				do a[sig].push_back((x=stk[top--]));while(x^v);
				ck+=a[sig].size()>=4;
			}
			low[p]=min(low[p],low[v]);
		}
	}
}
void solve(int p,int fa,int ed,int len){
	if(p==ed){res+=len==2;return;}
	for(ri i=0,v;i<e[p].size();++i)if((v=e[p][i])!=fa&&in[v])solve(v,p,ed,len+1);
}
inline void Solve(){
	for(ri i=1;i<=n;++i)if(!vis[i])if(!dfs(i,0)){puts("NO");return;}
	for(ri tt=1;tt<=n;++tt){
		if(dfn[tt])continue;
		ec=pc=tot=top=sig=ck=0;
		tarjan(tt);
		if(ck>2){puts("NO");return;}
		ec>>=1;
		if(ec<=pc)continue;
		if(ec>=pc+2){puts("NO");return;}
		int st=-1,ed=-1,pos=0;
		while(a[++pos].size()<4);
		for(ri i=0;i<a[pos].size();++i)in[a[pos][i]]=1,du[a[pos][i]]=0;
		for(ri i=0,u;i<a[pos].size();++i){
			u=a[pos][i];
			for(ri j=0;j<e[u].size();++j)if(in[e[u][j]])++du[u];
			if(du[u]==3)~st?ed=u:st=u;
		}
		res=0,solve(st,0,ed,0);
		if(res>=2)continue;
		puts("NO");
		return;
	}
	puts("YES");
}
int main(){
	for(ri tt=read();tt;--tt){
		n=read(),m=read();
		for(ri i=1;i<=n;++i)e[i].clear(),dfn[i]=vis[i]=in[i]=low[i]=0;
		for(ri i=1,u,v;i<=m;++i)u=read(),v=read(),e[u].push_back(v),e[v].push_back(u);
		Solve();
	}
	return 0;
}

求和
貌似是个让处理树上前缀和的送分题?
代码:

#include
#define ri register int
using namespace std;
typedef long long ll;
inline int read(){
	int ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
typedef long long ll;
const int N=3e5+5,mod=998244353;
inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(const int&a,const int&b){return a>=b?a-b:a-b+mod;}
inline int mul(const int&a,const int&b){return (ll)a*b%mod;}
int n,dep[N],top[N],fa[N],hson[N],siz[N],dis[N][51];
vector<int>e[N];
void dfs1(int p){
	siz[p]=1;
	dis[p][0]=1;
	for(ri i=1;i<=50;++i)dis[p][i]=mul(dis[p][i-1],dep[p]);
	for(ri i=0;i<=50;++i)dis[p][i]=add(dis[p][i],dis[fa[p]][i]);
	for(ri i=0,v;i<e[p].size();++i){
		if((v=e[p][i])==fa[p])continue;
		fa[v]=p,dep[v]=dep[p]+1,dfs1(v),siz[p]+=siz[v];
		if(siz[v]>siz[hson[p]])hson[p]=v;
	}
}
void dfs2(int p,int tp){
	top[p]=tp;
	if(!hson[p])return;
	dfs2(hson[p],tp);
	for(ri i=0,v;i<e[p].size();++i)if((v=e[p][i])!=fa[p]&&v!=hson[p])dfs2(v,v);
}
inline int lca(int x,int y){
	while(top[x]^top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		x=fa[top[x]];
	}
	return dep[x]<dep[y]?x:y;
}
inline void query(int x,int y,int k){
	int t=lca(x,y);
	cout<<dec(add(dis[x][k],dis[y][k]),add(dis[t][k],dis[fa[t]][k]))<<'\n';
}
int main(){
	n=read();
	for(ri i=1,u,v;i<n;++i)u=read(),v=read(),e[u].push_back(v),e[v].push_back(u);
	dfs1(1),dfs2(1,1);
	for(ri tt=read(),x,y;tt;--tt)x=read(),y=read(),query(x,y,read());
	exit(0);
}

双人猜数游戏
这个题并没有链接 b z o j bzoj bzoj不支持提答。
然后这个题我们用 d p dp dp
f i , j , t f_{i,j,t} fi,j,t表示一个数为 i i i另一个为 j j j t t t轮后能否都猜出来。
转移分几种情况讨论一下。
用来打表的代码借鉴了 z x y y y _ a k _ u u u o i zxyyy\_ak\_uuuoi zxyyy_ak_uuuoi的。
代码:

#include
#define ri register int
using namespace std;
typedef long long ll;
const int N=505;
int s,t;
bool f[N][N][20],flag,ff;
string name;
inline bool check1(int x,int y,int t){
	int res=0;
	for(ri i=s,X=x*y,up=sqrt(X),cnt=0;i<=up;++i)if(X==X/i*i)if(!t||!f[i][X/i][t-1]){++cnt,res=i;if(cnt>1)return 0;}
	return res==x;
}
inline bool check2(int x,int y,int t){
	int res=0;
	for(ri i=s,X=x+y,up=X>>1,cnt=0;i<=up;++i)if(!t||!f[i][X-i][t-1]){++cnt,res=i;if(cnt>1)return 0;}
	return res==x;
}
inline bool Check1(int x,int y){
	int res=0;
	for(ri i=s,X=x*y,up=sqrt(X),cnt=0;i<=up;++i)if(X==X/i*i)if((t<2||!f[i][X/i][t-2])&&f[i][X/i][t]){++cnt,res=i;if(cnt>1)return 0;}
	return res==x;
}
inline bool Check2(int x,int y){
	int res=0;
	for(ri i=s,X=x+y,up=X>>1,cnt=0;i<=up;++i)if((t<2||!f[i][X-i][t-2])&&f[i][X-i][t]){++cnt,res=i;if(cnt>1)return 0;}
	return res==x;
}
int main(){
	cin>>s>>name>>t,ff=flag=name=="Alice";
	for(ri tt=0;tt<=t;++tt,flag^=1)for(ri i=s;i<=500;++i)for(ri j=s;j<=500;++j){
		if(tt>=2)f[i][j][tt]=f[i][j][tt-2];
		if(!f[i][j][tt])f[i][j][tt]=flag?check1(i,j,tt):check2(i,j,tt);
	}
	for(ri sum=s<<1;;++sum){
		for(ri i=s,j;i<=sum>>1;++i){
			j=sum-i;
			if(!f[i][j][t])continue;
			flag=1;
			for(ri k=0;k<t;++k)if(f[i][j][k]){flag=0;break;}
			if(!flag)continue;
			if(((t&1)==ff?Check1(i,j):Check2(i,j)))return cout<<i<<' '<<j<<'\n',0;
		}
	}
	return 0;
}

链上二次求和
线段树菜题。
题解见这里。
代码:

#include
#define lc (p<<1)
#define rc (p<<1|1)
#define mid (l+r>>1)
#define ri register int
using namespace std;
inline int read(){
    int ans=0;
    char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch))ans=(ans<<1)+(ans<<3)+(ch^48),ch=getchar();
    return ans;
}
typedef long long ll;
const ll mod=1e9+7,N=2e5+5,inv=5e8+4;
int n,m;
ll a[N],s1[N],s2[N];
struct Node{ll l1,l2,l3,sum,a,b,c;}T[N<<2];
inline void pushup(int p){T[p].sum=(T[lc].sum+T[rc].sum)%mod;}
inline void pushnow(int p,ll a,ll b,ll c){
	T[p].a+=a,T[p].b+=b,T[p].c+=c,(T[p].sum+=a*T[p].l1+b*T[p].l2+c*T[p].l3)%=mod;
	if(T[p].a>=mod)T[p].a-=mod;
	if(T[p].b>=mod)T[p].b-=mod;
	if(T[p].c>=mod)T[p].c-=mod;
}
inline void pushdown(int p){
	if(!T[p].a&&!T[p].b&&!T[p].c)return;
	pushnow(lc,T[p].a,T[p].b,T[p].c),pushnow(rc,T[p].a,T[p].b,T[p].c);
	T[p].a=T[p].b=T[p].c=0;
}
inline void build(int p,int l,int r){
	T[p].l3=r-l+1,T[p].l2=s1[r]-(l?s1[l-1]:0),T[p].l1=s2[r]-(l?s2[l-1]:0);
	if(l==r){T[p].sum=a[l];return;}
	build(lc,l,mid),build(rc,mid+1,r),pushup(p);
}
inline void update(int p,int l,int r,int ql,int qr,ll a,ll b,ll c){
	if(ql>r||qr<l)return;
	if(ql<=l&&r<=qr)return pushnow(p,a,b,c);
	pushdown(p);
	if(qr<=mid)update(lc,l,mid,ql,qr,a,b,c);
	else if(ql>mid)update(rc,mid+1,r,ql,qr,a,b,c);
	else update(lc,l,mid,ql,mid,a,b,c),update(rc,mid+1,r,mid+1,qr,a,b,c);
	pushup(p);
}
inline ll query(int p,int l,int r,int ql,int qr){
	if(ql>r||qr<l)return 0;
	if(ql<=l&&r<=qr)return T[p].sum;
	pushdown(p);
	if(qr<=mid)return query(lc,l,mid,ql,qr);
	if(ql>mid)return query(rc,mid+1,r,ql,qr);
	return (query(lc,l,mid,ql,mid)+query(rc,mid+1,r,mid+1,qr))%mod;
}
int main(){
	n=read(),m=read();
	for(ri i=1;i<=n;++i)a[i]=(read()+a[i-1])%mod,s1[i]=(s1[i-1]+i)%mod,s2[i]=(s2[i-1]+(ll)i*i%mod)%mod;
	for(ri i=1;i<=n;++i)(a[i]+=a[i-1])%=mod;
	build(1,1,n);
	while(m--){
		int op=read(),l=read(),r=read(),len;
		ll v;
		if(l>r)swap(l,r);
		if(op==1){
			v=read(),len=r-l+1;
			ll a=v*inv%mod,b=v*(3-2*l+mod)%mod*inv%mod,c=v*(l-1)%mod*(l-2)%mod*inv%mod;
			update(1,1,n,l,r,a,b,c);
			if(r==n)continue;
			a=0,b=v*len%mod,c=(((ll)(len+1)*len/2%mod*v-v*len%mod*r)%mod+mod)%mod;
			update(1,1,n,r+1,n,a,b,c);
		}
		else{
			l=max(l,1);
			ll s1=query(1,1,n,n,n)*(r-l+1)%mod,s2=query(1,1,n,max(l-1,1),r-1),s3=query(1,1,n,max(n-r,1),n-l);
			cout<<(s1-s2-s3+mod*3)%mod<<'\n';
		}
	}
}

治疗之雨
期望 d p dp dp中档题。
列出式子之后移项。
然后会发现这个东西是一个方程。
我们令 f i f_i fi表示还剩 i i i滴血时死掉的期望轮数,且 f 1 = x f_1=x f1=x,那么 f 2 , f 3 , . . . , f n f_2,f_3,...,f_n f2,f3,...,fn均可用 A x + B Ax+B Ax+B表示出来,然后最后把 x x x解出来就完了。
注意预处理系数时候的次数上界。
代码:

#include
#define ri register int
using namespace std;
inline int read(){
    int ans=0;
    char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
    return ans;
}
typedef long long ll;
const int mod=1e9+7,N=1505;
inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(const int&a,const int&b){return a>=b?a-b:a-b+mod;}
inline int mul(const int&a,const int&b){return (ll)a*b%mod;}
inline int ksm(int a,int p){int ret=1;for(;p;p>>=1,a=mul(a,a))if(p&1)ret=mul(ret,a);return ret;}
int n,P,k,m,p[N];
struct coe{
    int x,y;
    coe(int _x=0,int _y=0):x(_x),y(_y){}
    friend inline coe operator+(const coe&a,const coe&b){return coe(add(a.x,b.x),add(a.y,b.y));}
    friend inline coe operator-(const coe&a,const coe&b){return coe(dec(a.x,b.x),dec(a.y,b.y));}
    friend inline coe operator*(const coe&a,const int&b){return coe(mul(a.x,b),mul(a.y,b));}
}f[N];
inline void solve(){
    p[0]=ksm(mul(m,ksm(m+1,mod-2)),k);
    if(p[0]==1||!p[0]){puts("-1");return;}
    for(ri i=1,invm=ksm(m,mod-2),up=min(k,n);i<=up;++i)p[i]=mul(mul(p[i-1],k-i+1),mul(ksm(i,mod-2),invm));
    int inv=ksm(p[0],mod-2),x0;
    f[0]=coe(0,0),f[1]=coe(1,0);
    for(ri i=1;i<n;++i){
        f[i+1]=(f[i]-coe(0,1))*(m+1);
        for(ri j=1,up=min(i+1,k);j<=up;++j)f[i+1]=f[i+1]-(f[i+1-j]*p[j]);
        for(ri j=0,up=min(i,k);j<=up;++j)f[i+1]=f[i+1]-(f[i-j]*mul(p[j],m));
        f[i+1]=f[i+1]*inv;
    }
    coe tmp=coe(0,1);
    for(ri i=1,up=min(n,k);i<=up;++i)tmp=tmp+(f[n-i]*p[i]);
    tmp=(tmp*ksm(dec(1,p[0]),mod-2))-f[n];
    if(!tmp.x&&f[P].x){puts("-1");return;}
    x0=tmp.x?mul(mod-tmp.y,ksm(tmp.x,mod-2)):0;
    cout<<add(mul(f[P].x,x0),f[P].y)<<'\n';
}
int main(){
    for(ri tt=read();tt;--tt){
        n=read(),P=read(),m=read(),k=read();
        if(!k){puts("-1");continue;}
        if(!m){
            if(k==1&&n!=1)puts("-1");
            else{
                p[0]=0;
                for(ri i=1;i<n;++i)p[i]=p[max(i+1-k,0)]+1;
                p[n]=p[max(n-k,0)]+1;
                cout<<p[P]<<'\n';
            }
            continue;
        }
        solve();
    }
    return 0;
}

你可能感兴趣的:(#,题解)