HNOI2018题解

在此处输入标题

标签(空格分隔): 未分类


重做了一遍,本来以为很快的,结果搞了一天。。。

寻宝游戏

可以发现只有\(\&0\)\(|1\)会对答案有影响

那么对于每一位,我们只要知道最后一个\(\&1\)和最后一个\(|1\)谁近就可以了。

发现并不好做,我们可以把操作串也当成\(01\)串,如果\(\&=0,|=1\)好像并没有什么用,于是我们令\(\&=1,|=0\)发现这样刚好满足了我们需要的信息,设\(op\)为操作串,这一位串为\(a\),如果\(op\)字典序小于\(a\)最后会是\(1\),否则为\(0\)。(\(|1=01,\&0=10\)这就是字典序了,手玩也可以

那么我们把原串基数排序,那么一定可以重排成\(00...011...1\),否则无解。如果有解那答案就是第一个\(1\)串代表的十进数值减掉最后一个\(0\)串十进制数值。注意下边界条件。

\(code\)

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define gt getchar()
#define ll long long
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
typedef std::pair<int,int> P;
#define mk std::make_pair
#define fr first
#define sc second
inline int in()
{
	int k=0;char ch=gt;bool p=1;
	while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
	while(ch>'-')k=k*10+ch-'0',ch=gt;
	return p?k:-k;
}
const int YL=1e9+7,N=1005,M=5005;
inline int ksm(int a,int k){int r=1;while(k){if(k&1)r=1ll*r*a%YL;a=1ll*a*a%YL,k>>=1;}return r;}
inline int MO(const int &x){return x>=YL?x-YL:x;}
int pw[M],s[N][M],id[2][M],rk[M],res[M];
int main()
{
	int n=in(),m=in(),q=in();
	for(int i=1;i<=n;++i)
	{
		static char S[M];scanf("%s",S+1);
		for(int j=1;j<=m;++j)s[i][j]=S[j]-'0';
	}
	for(int i=1;i<=m;++i)id[0][i]=i;
	int now=0;pw[0]=1;
	for(int i=1;i<=n;++i)
	{
		now^=1;int cnt=0,tot=0;pw[i]=MO(pw[i-1]<<1);
		for(int j=1;j<=m;++j)cnt+=s[i][j]^1;
		for(int j=1;j<=m;++j)
			if(s[i][id[now^1][j]])id[now][++cnt]=id[now^1][j];
			else id[now][++tot]=id[now^1][j];
	}
	int *p=id[now];
	for(int i=1;i<=m;++i)rk[p[i]]=i;
	for(int i=1;i<=m;++i)
		for(int j=1;j<=n;++j)
			res[i]=MO(res[i]+s[j][i]*pw[j-1]);
	res[m+1]=pw[n];p[m+1]=m+1;
	while(q--)
	{
		static char S[M];scanf("%s",S+1);
		int mx=-1,mi=m+1;
		for(int i=1;i<=m;++i)
			if(S[i]=='0')mx=std::max(mx,rk[i]);
			else mi=std::min(mi,rk[i]);
		if(mx>=mi){puts("0");continue;}
		printf("%d\n",MO(res[p[mi]]-res[p[mx]]+YL));
	}
	return 0;
}

转盘

可以发现,走一圈是最优的。

那么即求\(min(max(T_j-j)+i)+n-1\)

楼房重建即可

\(code\)

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define gt getchar()
#define ll long long
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
typedef std::pair<int,int> P;
#define mk std::make_pair
#define fr first
#define sc second
inline int in()
{
	int k=0;char ch=gt;bool p=1;
	while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
	while(ch>'-')k=k*10+ch-'0',ch=gt;
	return p?k:-k;
}
const int N=2e5+5;
int mis[N<<2],mip[N<<2],mxv[N<<2],a[N];
#define lc k<<1
#define rc k<<1|1
#define ls l, mid ,lc
#define rs mid+1,r,rc
#define mid ((l+r)>>1)
int calc(int mh,int l,int r,int k)
{
	if(l==r)return std::max(mh,a[l])+l;int w=mxv[rc];
	if(mh>=w)return std::min(calc(mh,ls),mh+mid+1);
	else return std::min(calc(mh,rs),mis[k]);
}
inline void up(int l,int r,int k)
{
	mxv[k]=std::max(mxv[lc],mxv[rc]);
	mis[k]=calc(mxv[rc],ls);
}
void build(int l,int r,int k)
{
	if(l==r)return mxv[k]=a[l],void();
	build(ls),build(rs),up(l,r,k);
}
void upd(int l,int r,int k,int p)
{
	if(l==r)return mxv[k]=a[l],void();
	p<=mid?upd(ls,p):upd(rs,p);up(l,r,k);
}
int main()
{
	int n=in(),m=in(),op=in(),ans=0;
	for(int i=1;i<=n;++i)
		a[i]=a[i+n]=in(),a[i]-=i,a[n+i]-=n+i;
	build(1,n<<1,1);printf("%d\n",ans=mis[1]+n-1);
	for(int i=1;i<=m;++i)
	{
		int x=in()^op*ans,y=in()^op*ans;
		a[x]=y-x;a[x+n]=y-x-n;
		upd(1,n<<1,1,x),upd(1,n<<1,1,x+n);
		printf("%d\n",ans=mis[1]+n-1);
	}
	return 0;
}

毒瘤

之前的博客是我没理解清写的。

树的\(dp\)是基础,然后把返祖边们抠出来建虚树,枚举两端情况。

这里的\(f[u][0/1]\)是表示\(u\)\(0/1\)的时候,子树的方案数。

所以我们只要枚举返祖边的祖先点的状态就可以了。

然后处理转移系数\(xs[u][i=0/1][j=0/1]\)表示虚树上的父亲选\(i\),这个点选\(j\)的系数的转移系数。

注意到边是有影响的,所以不在虚树上的点的\(xs\)表示的是该点选\(i\),这个点子树内第一个虚点选\(j\)的系数,当我们发现这个\(xs\)转移到一个虚点时,直接把\(xs\)挂在后面那维代表的虚点上。

不在虚树上的点记得乘上这个点选\(0/1\)的方案数。

在虚树上的点的\(xs\)要使得\(xs[u][0][0]=xs[u][1][1]=1\)

转移的时候如果这个点被强制选了某个值,另一的\(dp\)初值必须为\(0\)

\(code\)

#include<vector>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define pb push_back
#define gt getchar()
#define ll long long
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
typedef std::pair<int,int> P;
#define mk std::make_pair
#define fr first
#define sc second
inline int in()
{
	int k=0;char ch=gt;bool p=1;
	while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
	while(ch>'-')k=k*10+ch-'0',ch=gt;
	return p?k:-k;
}
const int YL=998244353,N=1e5+5;typedef std::vector<int> vi;
inline int ksm(int a,int k){int r=1;while(k){if(k&1)r=1ll*r*a%YL;a=1ll*a*a%YL,k>>=1;}return r;}
inline int MO(const int &x){return x>=YL?x-YL:x;}
vi G[N],E[N];int xs[N][2][2],f[N][2],g[N][2],tsz[N],imp[N],o[N];
int Eu[N],Ev[N],tot,dep[N],fg[N][2],vis[N],tt;
void pre_dfs(int u,int pa=0)
{
	o[u]=++tt;
	for(int v:G[u])if(v==pa)continue;
		else if(!o[v])pre_dfs(v,u),tsz[u]+=tsz[v];
		else
		{
			imp[u]=1;
			if(o[u]<o[v])
				Eu[++tot]=u,Ev[tot]=v;
		}
	imp[u]|=tsz[u]>=2;tsz[u]=tsz[u]||imp[u];
}
void mul(int f[2][2],int g[2][2])
{
	int f00=f[0][0],f01=f[0][1];
	int f10=f[1][0],f11=f[1][1];
	g[0][0]=MO(f00+f10);
	g[0][1]=MO(f01+f11);
	g[1][0]=f00,g[1][1]=f01;
}
inline void init(int f[2][2]){f[0][0]=f[1][1]=1,f[0][1]=f[1][0]=0;}
int dfs(int u)
{
	vis[u]=g[u][0]=g[u][1]=1;int pos=0;
	for(int v:G[u])
		if(!vis[v])
		{
			int w=dfs(v);
			if(!w)
			{
				g[u][0]=1ll*g[u][0]*(g[v][1]+g[v][0])%YL;
				g[u][1]=1ll*g[u][1]*g[v][0]%YL;
			}
			else if(!imp[u])mul(xs[v],xs[u]),pos=w;
			else mul(xs[v],xs[w]),E[u].pb(w),pos=w;
		}
	if(imp[u])return init(xs[u]),u;
	xs[u][0][0]=1ll*xs[u][0][0]*g[u][0]%YL;
	xs[u][0][1]=1ll*xs[u][0][1]*g[u][0]%YL;
	xs[u][1][0]=1ll*xs[u][1][0]*g[u][1]%YL;
	xs[u][1][1]=1ll*xs[u][1][1]*g[u][1]%YL;
	return pos;
}
void dp(int u)
{
	f[u][0]=fg[u][1]?0:g[u][0];
	f[u][1]=fg[u][0]?0:g[u][1];
	for(int v:E[u])
	{
		dp(v);
		for(int i=0;i<2;++i)
			f[u][i]=(1ll*xs[v][i][0]*f[v][0]+1ll*xs[v][i][1]*f[v][1])%YL*f[u][i]%YL;
	}
}
int main()
{
	int n=in(),m=in(),ans=0;
	for(int i=1,u,v;i<=m;++i)
		u=in(),v=in(),G[u].pb(v),G[v].pb(u);
	pre_dfs(1),imp[1]=1,dfs(1);int mx=1<<tot;
	for(int i=0;i<mx;++i)
	{
		for(int j=0;j<tot;++j)
			if(i>>j&1)fg[Eu[j+1]][1]=1,fg[Ev[j+1]][0]=1;
			else fg[Eu[j+1]][0]=1;
		dp(1);ans=MO(ans+MO(f[1][1]+f[1][0]));		
		for(int j=0;j<tot;++j)
			if(i>>j&1)fg[Eu[j+1]][1]=0,fg[Ev[j+1]][0]=0;
			else fg[Eu[j+1]][0]=0;
	}
	printf("%d\n",ans);
	return 0;
}

游戏

我们发现如果一个点能到\(L\),且另一个点能到它,那么另一个点肯定能到\(L\),所以我们预处理\(L[i],R[i]\)\(i\)点能扩大的最大范围。我们需要安排一个顺序使得他们最优。

如果一扇门\(x,x+1\)的钥匙在\(1-x\)则肯定要先转移\(x+1\)再转移\(x\),反之亦然。

于是我们可以拓扑排序。

yyb的做法是假的

问题的关键在于门是不满的,所以有很多没有关系的点之间跳来跳去,复杂度就假了。

但是由于数据只卡了正着做的,没卡反着做的,于是他的乱搞就能过(他写了两篇乱搞(小声。

那我们用并查集把没有门的点缩起来就\(ok\)了。

\(code\)

#include<queue>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define gt getchar()
#define ll long long
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
typedef std::pair<int,int> P;
#define mk std::make_pair
#define fr first
#define sc second
inline int in()
{
	int k=0;char ch=gt;bool p=1;
	while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
	while(ch>'-')k=k*10+ch-'0',ch=gt;
	return p?k:-k;
}
const int N=1e6+5;
int head[N],to[N],du[N],p[N],L[N],R[N],nxt[N],cnt,key[N],tot,n,fa[N];
inline void add(int u,int v){to[++cnt]=v,nxt[cnt]=head[u],head[u]=cnt,++du[v];}
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
inline void work(int u)
{
	int l=L[u],r=R[u],nl,nr;
	while(1)
	{
		nl=l,nr=r;
		while(l>1&&(!key[l-1]||(l<=key[l-1]&&key[l-1]<=r)))l=L[find(l-1)];
		while(r<n&&(!key[ r ]||(l<=key[ r ]&&key[ r ]<=r)))r=R[find(r+1)];
		if(nl==l&&nr==r)break;
	}
	L[u]=l,R[u]=r;
}
int main()
{
	n=in();int m=in(),q=in();
	for(int i=1,x,y;i<=m;++i)x=in(),y=in(),key[x]=y;
	for(int i=1;i<=n;++i)fa[i]=i;
	for(int i=1;i<n;++i)if(!key[i])fa[i+1]=find(i);
	for(int i=1;i<n;++i)
		if(key[i])
		{
			if(key[i]<=i)add(find(i+1),find(i));
			else add(find(i),find(i+1));
		}
	std::queue<int>Q;
	for(int i=1;i<=n;++i)if(fa[i]==i&&!du[i])Q.push(i);
	while(!Q.empty())
	{
		int u=p[++tot]=Q.front();Q.pop();
		for(int i=head[u];i;i=nxt[i])
			if(!--du[to[i]])Q.push(to[i]);
	}
	for(int i=1;i<=n;++i)R[find(i)]=i;
	for(int i=n;i>=1;--i)L[find(i)]=i;
	for(int i=1;i<=tot;++i)work(p[i]);
	while(q--){int x=find(in()),y=find(in());puts(L[x]<=y&&y<=R[x]?"YES":"NO");}
	return 0;
}

排列

先把依赖关系的\(DAG\)建出来。

然后就是贪心,小的一定要尽量放在前面。

考虑当前最小值,它在父亲节点删掉后一定会被删。

所以可以并起来,然后我们现在是考虑一堆序列的顺序,推下式子发现只要平均值小就一定先选,就没了。

\(code\)

#include<cstdlib>
#include<vector>
#include<queue>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define gt getchar()
#define ll long long
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
#define mk std::make_pair
#define fr first
#define sc second
#define double long double
typedef std::pair<double,int> P;
inline int in()
{
	int k=0;char ch=gt;bool p=1;
	while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
	while(ch>'-')k=k*10+ch-'0',ch=gt;
	return p?k:-k;
}
typedef std::vector<int> vi;
const int N=5e5+5;vi G[N];
const double eps=1e-6;
int o[N],sz[N],fa[N],ff[N];ll w[N];
struct Queue
{
	std::priority_queue<P>Q1,Q2;
	void push(P x){Q1.push(x);}
	void erase(P x){Q2.push(x);}
	void upd(){while(!Q2.empty()&&Q1.top()==Q2.top())Q1.pop(),Q2.pop();}
	inline void pop(){upd();Q1.pop();}
	inline P top(){upd();return Q1.top();}
}Q;
int dfs(int u)
{
	int ans=u!=0;o[u]=1;
	for(int v:G[u])
		if(o[v])puts("-1"),exit(0);
		else ans+=dfs(v);return ans;
}
int find(int x){return x==ff[x]?x:ff[x]=find(ff[x]);}
int main()
{
	int n=in();ll ans=0;
	for(int i=1;i<=n;++i)G[fa[i]=in()].push_back(i);
	for(int i=1;i<=n;++i)ans+=w[i]=in();
	if(dfs(0)!=n)return puts("-1"),0;
	for(int i=1;i<=n;++i)ff[i]=i,sz[i]=1;
	for(int i=1;i<=n;++i)Q.push(mk(-(double)w[i],i));
	for(int i=1;i<=n;++i)
	{
		P now=Q.top();Q.pop();int u=find(now.sc),v=find(fa[u]);
		if(v)Q.erase(mk(-(double)w[v]/sz[v],v));
		ans+=w[u]*sz[v],w[v]+=w[u],sz[v]+=sz[u],ff[u]=v;
		if(v)Q.push(mk(-(double)w[v]/sz[v],v));
	}
	printf("%lld\n",ans);
	return 0;
}

道路

普及\(dp\),设\(F[i][j][k]\)表示第\(i\)个城市,没修的公路有\(j\)条,没修的铁路有\(k\)条的最小代价。

叶子节点直接算,非叶子节点枚举修什么。考场上好像卡空间,用分治的\(fft\)的卡空间技巧就行了

\(code\)

#include<vector>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define gt getchar()
#define ll long long
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
typedef std::pair<int,int> P;
#define mk std::make_pair
#define fr first
#define sc second
inline int in()
{
	int k=0;char ch=gt;bool p=1;
	while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
	while(ch>'-')k=k*10+ch-'0',ch=gt;
	return p?k:-k;
}
typedef std::vector<int> vi;
const int N=20005;vi G[N];
ll f[N][41][41],a[N],b[N],c[N];
ll dfs(int u,int L,int R)
{
	if(u<0)return c[-u]*(a[-u]+L)*(b[-u]+R);
	if(~f[u][L][R])return f[u][L][R];int lc=G[u][0],rc=G[u][1];
	return f[u][L][R]=std::min(dfs(lc,L+1,R)+dfs(rc,L,R),dfs(lc,L,R)+dfs(rc,L,R+1));
}
int main()
{
	int n=in();
	for(int i=1;i<n;++i)
	{
		int s=in(),t=in();
		G[i].push_back(s),G[i].push_back(t);
	}
	for(int i=1;i<=n;++i)a[i]=in(),b[i]=in(),c[i]=in();
	memset(f,-1,sizeof f);printf("%lld\n",dfs(1,0,0));
	return 0;
}

posted @ 2019-04-03 22:17  Cgod  阅读(264)  评论(1编辑  收藏  举报