HNOI 2018 简要题解

寻宝游戏
毒瘤题。
估计考试只会前 30 p t s 30pts 30pts暴力然后果断走人。
正解是考虑到一个数 & 1 \&1 &1 ∣ 0 |0 0都没有变化, & 0 \&0 &0会强制变成 0 0 0, ∣ 1 |1 1会强制变成 1 1 1,于是如果结果是 1 1 1说明最后一个出现的 ∣ 1 |1 1在最后一个出现的 & 0 \&0 &0的后面,这样我们将所有的操作集合拿来搞成一个 01 01 01串并把所有 01 01 01串反向之后就可以求出可行范围然后得出答案了。
代码:

#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<<1)+(ans<<3)+(ch^48),ch=getchar();
	return ans;
}
const int mod=1e9+7,N=5005;
int n,m,q,Bit[N],sum[N],c[2],rk[N],lastrk[N],l,r;
char s[N];
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 void update(int&a,const int&b){a=add(a,b);}
inline void init(){
	for(ri i=Bit[0]=1;i<=n;++i)Bit[i]=(Bit[i-1]<<1)%mod;
    for(ri i=1;i<=m;++i)rk[i]=i;
}
inline void Sort(){
    for(ri i=c[0]=c[1]=0;i<n;++i,c[0]=c[1]=0){
        scanf("%s",s+1);
        for(ri j=1;j<=m;++j)++c[s[j]^48],update(sum[j],(s[j]^48)*Bit[i]);
        c[1]+=c[0];
        for(ri j=m;j;--j)lastrk[c[s[rk[j]]^48]--]=rk[j];
		swap(lastrk,rk);
    }
}
int main(){
    n=read(),m=read(),q=read(),init(),Sort();
	while(q--){
        scanf("%s",s+1),l=0,r=m+1;
        for(ri i=1;i<=m;++i)if(s[rk[i]]^48){r=i;break;}
        for(ri i=m;i;--i)if(!(s[rk[i]]^48)){l=i;break;}
        l>=r?cout<<0<<'\n':cout<<(dec(r>m?Bit[n]:sum[rk[r]],sum[rk[l]]))<<'\n';
    }
	return 0;
}

转盘
考虑时间倒流转化问题:
想象成从 T T T时刻开始每个时刻可以倒着走一步或者停住,每个物品有一个消失时间,要在所有物品消失之前经过它们。
为了方便我们断环为链
假设是从 i i i开始倒退 ( n ≤ i < 2 n ) (n\le i<2n) (ni<2n),则有 T − ( i − j ) ≥ t j ⇒ T ≥ ( t j − j ) + i ⇒ T m i n = max ⁡ { T j − j } + i T-(i-j)\ge t_j\Rightarrow T\ge(t_j-j)+i\Rightarrow T_{min}=\max\{T_j-j\}+i T(ij)tjT(tjj)+iTmin=max{Tjj}+i
a i = t i − i a_i=t_i-i ai=tii
那么 A n s = m i n n ≤ i < 2 n { m a x i − n < j ≤ i { a j } + i } Ans=min_{n\le i<2n}\{max_{i-n<j\le i}\{a_j\}+i\} Ans=minni<2n{maxin<ji{aj}+i}
因为 a i > a i + n , i ≤ n a_i>a_{i+n},i\le n ai>ai+n,in
所以 A n s = m i n 1 ≤ i ≤ n { m a x i ≤ j ≤ 2 n { a j } + i } + n − 1 Ans=min_{1\le i\le n}\{max_{i\le j\le 2n}\{a_j\}+i\}+n-1 Ans=min1in{maxij2n{aj}+i}+n1
然后发现就是唯一一个后缀 m a x max max m i n min min,这个可以用线段树维护单调栈实现。
代码:

#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<<1)+(ans<<3)+(ch^48),ch=getchar();
	return ans;
}
const int N=1e5+5,inf=0x3f3f3f3f;
int n,m,lastans,a[N<<1],type;
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,val,mx;}T[N<<3];
	inline int query(int p,int lim,int v){
		if(T[p].l==T[p].r)return min(T[p].l==lim?inf:T[p].l+1+v,T[p].l+max(v,T[p].mx));
		if(v>=T[rc].mx)return query(lc,lim,v);
		return min(T[p].val,query(rc,lim,v));
	}
	inline void pushup(int p){T[p].mx=max(T[lc].mx,T[rc].mx),T[p].val=query(lc,mid,T[rc].mx);}
	inline void build(int p,int l,int r){
		T[p].l=l,T[p].r=r;
		if(l==r){T[p].mx=a[l];return;}
		build(lc,l,mid),build(rc,mid+1,r),pushup(p);
	}
	inline void update(int p,int k,int v){
		if(T[p].l==T[p].r){T[p].mx=v;return;}
		update(k<=mid?lc:rc,k,v),pushup(p);
	}
	#undef lc
	#undef rc 
	#undef mid
}
int main(){
  	n=read(),m=read(),type=read();
  	for(ri i=1;i<=n;++i)a[i]=read()-i,a[i+n]=a[i]-n;
  	SGT::build(1,1,n<<1);
  	cout<<(lastans=SGT::T[1].val+n-1)<<'\n';
  	for(ri x,y;m;--m){
  		x=read()^(type*lastans),y=read()^(type*lastans);
  		SGT::update(1,x,y-x),SGT::update(1,x+n,y-x-n);
		cout<<(lastans=SGT::T[1].val+n-1)<<'\n';
  	}
	return 0;
}

毒瘤
枚举边的状态+树形 d p dp dp这种暴力 75 75 75应该都是一眼会吧。
但这样会 T L E TLE TLE掉最后几个点 出题人毒瘤
然后需要建出这棵树的虚树来优化每次 d p dp dp的时间。
然后就没了。
代码:

#include
#define ri register int
#define fi first
#define se second
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 pair<int,int> pii;
typedef long long ll;
const int N=1e5+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 P[N][2],n,m,ans=0,f[N][2],siz[N],tot=0,dfn[N];
vector<int>e[N];
vector<pii>G;
bool vis[N],key[N],ban[N][2];
void Dfs(int p,int fa){
    dfn[p]=++tot;
    for(ri i=0,v;i<e[p].size();++i){
        if((v=e[p][i])==fa)continue;
		if(dfn[v]){key[p]=1;if(dfn[p]<dfn[v])G.push_back(pii(p,v));continue;}
        else Dfs(v,p),siz[p]+=siz[v];
    }
    key[p]|=siz[p]>=2;
	siz[p]=siz[p]||key[p];
}
struct Coef{
	int x,y;
	Coef(int _x=0,int _y=0):x(_x),y(_y){}
	friend inline Coef operator+(const Coef&a,const Coef&b){return Coef(add(a.x,b.x),add(a.y,b.y));}
	friend inline Coef operator*(const Coef&a,const int&b){return Coef(mul(a.x,b),mul(a.y,b));}
}k[N][2];
struct Node{int v;Coef a,b;};
vector<Node>E[N];
inline int Build(int p){
	P[p][0]=P[p][1]=vis[p]=1;
	int ret=0;
	for(ri w,i=0,v;i<e[p].size();++i){
		if(vis[v=e[p][i]])continue;
		w=Build(v);
		if(!w)P[p][1]=mul(P[p][1],P[v][0]),P[p][0]=mul(P[p][0],add(P[v][0],P[v][1]));
		else if(key[p])E[p].push_back((Node){w,k[v][0]+k[v][1],k[v][0]});
		else k[p][0]=k[v][0]+k[v][1],k[p][1]=k[v][0],ret=w;
	}
	if(key[p])k[p][0]=Coef(1,0),k[p][1]=Coef(0,1),ret=p;
	else k[p][0]=k[p][0]*P[p][0],k[p][1]=k[p][1]*P[p][1];
	return ret;
}
inline void solve(int p){
	f[p][0]=ban[p][0]?0:P[p][0];
	f[p][1]=ban[p][1]?0:P[p][1];
	for(ri i=0,v;i<E[p].size();++i){
		solve((v=E[p][i].v));
		f[p][0]=mul(f[p][0],add(mul(E[p][i].a.x,f[v][0]),mul(E[p][i].a.y,f[v][1])));
		f[p][1]=mul(f[p][1],add(mul(E[p][i].b.x,f[v][0]),mul(E[p][i].b.y,f[v][1])));
	}
}
inline void init(int sta){
    for(ri i=0,x,y,tmp;i<G.size();++i){
        tmp=(sta>>i)&1,x=G[i].fi,y=G[i].se;
        if(!tmp)ban[x][1]=1;
        else ban[x][0]=ban[y][1]=1;
    }
    solve(1),ans=add(ans,add(f[1][0],f[1][1]));
    for(ri i=0,x,y,tmp;i<G.size();++i){
        tmp=(sta>>i)&1,x=G[i].fi,y=G[i].se;
        if(!tmp)ban[x][1]=0;
        else ban[x][0]=ban[y][1]=0;
    }
}
int main(){
  	n=read(),m=read();
  	for(ri i=1,u,v;i<=m;++i)u=read(),v=read(),e[u].push_back(v),e[v].push_back(u);
	Dfs(1,0),key[1]=1,Build(1);
  	for(ri sta=0,up=1<<G.size();sta<up;++sta)init(sta);
  	cout<<ans;
    return 0;
}

游戏
对于两个可以不能相通的块,如果钥匙在左边,那么可能可以从左到右但并不能从右到左,我们对于这种情况建一条有向边,然后动态维护一个拓扑排序即可。
代码:

#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=1e6+5;
int n,m,q,goa[N],l[N],r[N],mp[N],du[N],tot=0,x,y;
vector<int>e[N];
inline void add(const int&u,const int&v){e[u].push_back(v),++du[v];}
inline void extend(int i){
	static int t1,t2;
	while(1){
        t1=l[i]^1,t2=r[i]^n;
        if(t1)(!goa[l[i]-1])||(goa[l[i]-1]>=l[i]&&goa[l[i]-1]<=r[i])?l[i]=l[mp[l[i]-1]]:t1=0;
        if(t2)(!goa[r[i]])||(goa[r[i]]>=l[i]&&goa[r[i]]<=r[i])?r[i]=r[mp[r[i]+1]]:t2=0;
        if(!(t1+t2))break;
    }
}
inline void topsort(){
	static int q[N],hd,tl;
	hd=1,tl=0;
    for(ri i=1;i<=n;i++)if(mp[i]==i&&!du[i])q[++tl]=i;
    while(hd<=tl){
        int p=q[hd++];
		extend(p);
		for(ri i=0,v;i<e[p].size();++i)if(!(--du[v=e[p][i]]))q[++tl]=v;
    }
}
int main(){
    n=read(),m=read(),q=read();
    for(ri i=1;i<=n;i++)mp[i]=l[i]=r[i]=i;
    while(m--)x=read(),goa[x]=read();
    for(ri i=1;i<=n;i++)if(!goa[i])mp[i+1]=mp[i];else goa[i]<=i?add(mp[i+1],mp[i]):add(mp[i],mp[i+1]);
    for(ri i=1;i<=n;i++)l[mp[i]]=min(l[mp[i]],l[i]),r[mp[i]]=max(r[mp[i]],r[i]);
    topsort();
    for(ri i=1;i<=n;i++)l[i]=l[mp[i]],r[i]=r[mp[i]];
    while(q--)x=read(),y=read(),puts(l[x]<=y&&y<=r[x]?"YES":"NO");
    return 0;
}

排列
考虑按照题意建边建出来是一棵外向树。
然后显然应该按某一种拓扑序来形成序列(注意这个时候要判环来看是否合法)
这个东西可以用并查集+堆来搞一下,比较的依据是一个连通块的平均权值。
代码:

#include
#define ri register int
#define fi first
#define se second
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;
}
const int N=5e5+5;
typedef long long ll;
typedef pair<long double,int> pii;
int n,m,siz[N],fa[N],anc[N];
ll w[N],ans=0;
inline int find(const int&x){return x^anc[x]?anc[x]=find(anc[x]):x;}
struct In_Out_queue{
	priority_queue<pii,vector<pii>,greater<pii> >a,b;
	inline void push(const pii&x){a.push(x);}
	inline void del(const pii&x){b.push(x);}
	inline pii top(){while(b.size()&&a.top()==b.top())a.pop(),b.pop();return a.top();}
	inline void pop(){while(b.size()&&a.top()==b.top())a.pop(),b.pop();a.pop();}
}q;
int main(){
	n=read();
	for(ri i=0;i<=n;++i)anc[i]=i;
	for(ri i=1;i<=n;++i){
		fa[i]=read();
		if(anc[find(i)]^anc[find(fa[i])])anc[find(i)]=find(fa[i]);
		else return puts("-1"),0;
	}
	ll ans=0;
	anc[0]=0;
	for(ri i=1;i<=n;++i)ans+=(w[i]=read()),anc[i]=i,siz[i]=1,q.push(pii((long double)w[i],i));
	for(ri i=1;i<=n;++i){
		int x=q.top().se,y=find(fa[x]);
		q.pop();
		if(y)q.del(pii((long double)w[y]/siz[y],y));
		ans+=(ll)siz[y]*w[x],siz[y]+=siz[x],anc[x]=y,w[y]+=w[x];
		if(y)q.push(pii((long double)w[y]/siz[y],y));
	}
	cout<<ans;
    return 0;
}

道路
HNOI2018最水的一道没有之一没错就是它。 并不是反话
由于题目中给了深度保证,于是直接暴力三维树形 d p dp dp就能过了。
记忆化搜索真的好写
代码:

#include
#define ri register int
using namespace std;  
typedef long long ll;
inline int read(){
    int ans=0;
    bool f=1;
    char ch=getchar();
    while(!isdigit(ch))f^=ch=='-',ch=getchar();
    while(isdigit(ch))ans=(ans<<1)+(ans<<3)+(ch^48),ch=getchar();
    return f?ans:-ans;
}
const int N=20005;
ll a[N],b[N],c[N],f[N][45][45];
int son[N][2],n;
ll dfs(int p,int x,int y){
	if(p>=n)return c[p-n+1]*(a[p-n+1]+x)*(b[p-n+1]+y);
	if(f[p][x][y]^f[0][0][0])return f[p][x][y];
	return f[p][x][y]=min(dfs(son[p][0],x,y)+dfs(son[p][1],x,y+1),dfs(son[p][0],x+1,y)+dfs(son[p][1],x,y));
}
int main(){
	n=read();
	memset(f,127,sizeof(f));
	for(ri i=1,x,y;i<n;++i){
		x=read(),y=read();
		if(x<0)x=n-x-1;
		if(y<0)y=n-y-1;
		son[i][0]=x,son[i][1]=y;
	}
	for(ri i=1;i<=n;++i)a[i]=read(),b[i]=read(),c[i]=read();
	return cout<<dfs(1,0,0),0;
}

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