HNOI2018day1简要题解

话说我怎么这么菜啊,做过的题还是不会做!


*game

以前写的题解

补充:

怎么从限制想到将操作序列转成01的呢?

∣ 0 , & 1 |0,\&1 0,&1为无用操作, ∣ 1 , & 0 |1,\&0 1,&0为有用操作,若这一位最后得到了 1 1 1,则必然最后一个操作是 ∣ 1 |1 1——也就是只比较最后一个有用的位——类似于字典序大小比较(第一个不同的位),然后就可以联想到 ∣ → 0 , & → 1 |\to 0,\& \to 1 0,&1了。

发现枚举最后一个有用操作无法兼顾每一位,这时必须要换一种思路。

#include<bits/stdc++.h>
using namespace std;
const int N=5005,mod=1e9+7;

int n,m,q,bin[N],rk[N],c[N],cnt[N];
int g[2][N];
char s[1002][N],t[N];

inline void ad(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline void dc(int &x,int y){x-=y;if(x<0) x+=mod;}
inline int inc(int x,int y){x+=y;return x>=mod?x-mod:x;}
inline int dec(int x,int y){x-=y;return x<0?x+mod:x;}

int main(){
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	int i,j,k,a,b,L,R;bin[0]=1;
	scanf("%d%d%d",&n,&m,&q);
	for(i=1;i<=n;++i) bin[i]=inc(bin[i-1],bin[i-1]);
	for(i=1;i<=n;++i){
		scanf("%s",s[i]+1);
		for(j=1;j<=m;++j) s[i][j]-='0';
	}
	for(i=1;i<=m;++i) c[i]=i;
	for(i=1;i<=n;++i){
		g[0][0]=g[1][0]=0;
		for(j=1;j<=m;++j){
		   k=s[i][c[j]];
		   g[k][++g[k][0]]=c[j];
		}
		a=g[0][0];b=g[1][0];
		for(j=1;j<=a;++j) rk[j]=g[0][j];
		for(j=1;j<=b;++j) rk[a+j]=g[1][j];
		memcpy(c,rk,(m+1)<<2);
	}
	for(i=1;i<=m;++i)
	  rk[c[i]]=i;
	for(i=1;i<=m;++i)
		for(j=1;j<=n;++j)
		 if(s[j][c[i]]) ad(cnt[i],bin[j-1]);
	cnt[m+1]=bin[n];
	for(;q;--q){
		L=0;R=m+1;scanf("%s",t+1);
		for(i=1;i<=m;++i)
		 if(t[i]=='1') R=min(R,rk[i]);
		 else L=max(L,rk[i]);
		 if(L>=R) puts("0");
		 else printf("%d\n",dec(cnt[R],cnt[L]));
	}
	fclose(stdin);fclose(stdout);
	return 0;
}

*circle

以前写的题解
十分详细

后一部分单调栈转成后缀max再转线段树的操作还是觉得非常妙(用多一个 log ⁡ \log log的代价维护除了区间答案)

#include<bits/stdc++.h>
#define mid (l+r>>1)
#define lc k<<1
#define rc k<<1|1
#define lcc lc,l,mid
#define rcc rc,mid+1,r 
using namespace std;
const int N=2e5+10;

int n,m,ans,lim,typ,t[N];
int mx[N<<2],val[N<<2];

int upd(int k,int v,int l,int r)
{
	if(l==r) return l+max(v,mx[k]);
	if(v>=mx[k]) return l+v;
	if(v>=mx[rc]) return min(mid+1+v,upd(lc,v,l,mid));
	return min(val[k],upd(rc,v,mid+1,r)); 
}

inline void pu(int k,int l,int r)
{
	val[k]=upd(lc,mx[rc],l,mid);
	mx[k]=max(mx[lc],mx[rc]);
} 

void build(int k,int l,int r)
{
	if(l==r) {val[k]=l+t[l];mx[k]=t[l];return;}
	build(lcc);build(rcc);pu(k,l,r);
}

void chg(int k,int l,int r,int pos,int v)
{
	if(l==r) {val[k]=l+v;mx[k]=v;return;}
	if(pos<=mid) chg(lcc,pos,v);else chg(rcc,pos,v);
	pu(k,l,r);
}

int main(){
	freopen("circle.in","r",stdin);
	freopen("circle.out","w",stdout);
	int i,j,x,y;scanf("%d%d%d",&n,&m,&typ);
	for(i=1;i<=n;++i) {scanf("%d",&j);t[i]=j-i;t[i+n]=j-i-n;}
	lim=(n<<1);build(1,1,lim);
	ans=val[1]+n-1;printf("%d\n",ans); 
	for(;m;--m){
		scanf("%d%d",&x,&y);if(typ) x^=ans,y^=ans;
		chg(1,1,lim,x,y-x);chg(1,1,lim,x+n,y-x-n);
		ans=val[1]+n-1;printf("%d\n",ans);
	}
	fclose(stdin);fclose(stdout);
	return 0;
}

duliu

也不算太毒瘤吧,至少部分分很多。

f [ x ] [ 1 / 0 ] f[x][1/0] f[x][1/0]分别表示 x x x选或不选时的子树方案和。

比较容易想到枚举 2 m − n + 1 2^{m-n+1} 2mn+1枚举每条非树边的选择状态——对于 ( u , v ) (u,v) (u,v),枚举 u u u是否选择:若选择则 v v v必须不选,否则 v v v可以选也可以不选。 O ( 2 10 n ) O(2^{10}n) O(210n)已经可以过掉大部分点了。

考虑把与这 10 10 10条边相关的20个点提出来做虚树,实际上在枚举状态时虚数外部的DP几乎是重复的:对于虚树上的一条边 ( u , v ) (u,v) (u,v)(设 u u u为父亲),可以把非虚树点的DP值都看成常数,那么 f [ u ] [ 1 / 0 ] f[u][1/0] f[u][1/0]必然可以表现成 x ⋅ f [ v ] [ 0 ] + y ⋅ f [ v ] [ 1 ] x·f[v][0]+y·f[v][1] xf[v][0]+yf[v][1]的形式,其中 x , y x,y x,y为常数系数。

O ( n ) O(n) O(n)DP一遍求出虚树上每个点向父亲转移的常数系数。然后还是 2 m − n + 1 2^{m-n+1} 2mn+1枚举状态在虚树上DP。

此题比较口胡,具体实现非常有技巧性,建议代码实现一下。

具体见代码(几乎是照着这篇写的,个人感觉写得很好)

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1e5+50,mod=998244353;

int n,m,df[N],dfn,sn[N],cnt;
int cn[N][2],wb[N][2],f[N][2],ans;
int head[N],to[N<<1],nxt[N<<1],tot;
bool in[N],vs[N];

inline void ad(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline void dc(int &x,int y){x-=y;if(x<0) x+=mod;}
inline int inc(int x,int y){x+=y;return x>=mod?x-mod:x;}
inline int dec(int x,int y){x-=y;return x<0?x+mod:x;}

struct ob{
	int x,y;
	ob(int x_=0,int y_=0):x(x_),y(y_){};
	ob operator +(const ob&ky){return ob(inc(x,ky.x),inc(y,ky.y));}
	ob operator *(const int &ky){return ob((ll)x*ky%mod,(ll)y*ky%mod);}
}le[N],nb[N][2];
struct dr{int v;ob a,b;};
vector<dr>g[N];

inline void lk(int u,int v)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}


int dfs(int x,int fr)
{
	int i,j;df[x]=++dfn;
	for(i=head[x];i;i=nxt[i]){
		j=to[i];if(j==fr) continue;
		if(!df[j]) {sn[x]+=dfs(j,x);continue;}
		in[x]=true;
		if(df[x]<df[j]) le[++cnt]=ob(x,j);
	}
	if(sn[x]>1) in[x]=true;
	return (in[x] || sn[x]>0)?1:0;
}

int mk(int x)
{
	int re=0,res,v,i,j;
	wb[x][0]=wb[x][1]=1;vs[x]=true;
	for(i=head[x];i;i=nxt[i]){
		j=to[i];if(vs[j]) continue;
		res=mk(j);
		if(!res){
			wb[x][0]=(ll)wb[x][0]*inc(wb[j][0],wb[j][1])%mod;
			wb[x][1]=(ll)wb[x][1]*wb[j][0]%mod;
		}else if(in[x]) g[x].pb((dr){res,nb[j][0]+nb[j][1],nb[j][0]});
		else nb[x][0]=nb[j][0]+nb[j][1],nb[x][1]=nb[j][0],re=res;
	}
	if(in[x]) nb[x][0]=ob(1,0),nb[x][1]=ob(0,1),re=x;
	else nb[x][0]=nb[x][0]*wb[x][0],nb[x][1]=nb[x][1]*wb[x][1];
	return re;
}

void cal(int x)
{
	int i,j,a,b;dr tp;
	f[x][0]=cn[x][1]?0:wb[x][0];f[x][1]=cn[x][0]?0:wb[x][1];
	for(i=g[x].size()-1;i>=0;--i){
		tp=g[x][i];cal(tp.v);a=f[tp.v][0];b=f[tp.v][1];
		f[x][0]=(ll)f[x][0]*inc((ll)tp.a.x*a%mod,(ll)tp.a.y*b%mod)%mod;
		f[x][1]=(ll)f[x][1]*inc((ll)tp.b.x*a%mod,(ll)tp.b.y*b%mod)%mod;
	}
}

int main(){
	freopen("duliu.in","r",stdin);
	freopen("duliu.out","w",stdout);
	int i,j,x,y,S,num;in[1]=true;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;++i){scanf("%d%d",&x,&y);lk(x,y);lk(y,x);}
    dfs(1,0);mk(1);S=1<<cnt;
    for(i=0;i<S;++i){
    	//cn[i][1/0]=1 勒令 i 选/不选 
    	for(j=0;j<cnt;++j)
    	 if((i>>j)&1) cn[le[j+1].x][1]=cn[le[j+1].y][0]=1;
    	 else cn[le[j+1].x][0]=1;
    	cal(1);ad(ans,inc(f[1][0],f[1][1]));
    	for(j=0;j<cnt;++j)
    	 if((i>>j)&1) cn[le[j+1].x][1]=cn[le[j+1].y][0]=0;
    	 else cn[le[j+1].x][0]=0;
	}
	printf("%d",ans);
	fclose(stdin);fclose(stdout);
	return 0;
}

总结

T1可能智障选手已经想不出正解了,暴力无从下手?
T2最后一步也大概是想不出来了
T3信仰暴力,虚树求系数以目前代码能力估计敲不出来了

三题暴力告辞

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值