6.10考试总结(NOIP模拟6)

即使天无雨,我亦留此地。

前言

就这题考的不咋样果然还挺难改的。。

Screenshot_2021-06-10 每日排行 - HZOJ2020.png

T1 辣鸡

前言

我做梦都没想到这题正解是模拟,打模拟赛的时候看错题面以为是\(n\times n\)的矩阵,喜提0pts

解题思路

氢键的数量计算起来无非主要就是两种情况:

  • 整个矩阵里面的
  • 各个矩阵之间相邻的

整个矩阵里的比较好算:

\(\sum\limits_{i=1}^{n}(2\times q[i].x_2-q[i].x_1)\times(q[i].y_2-q[i].y_1)\)

主要是矩阵之间的比较难整,鉴于x和y相邻的情况差不多,以下只讲述x的情况,前提是两个矩形的比较近的两个横坐标相差为1。假设矩形A为当前矩形,B为正在匹配的矩形分为4种:

  1. B的边长大于A的(A的两端都在B的\([x_1,x_2]\)区间中):产生氢键数为 \(2 \times A\) 的纵向长度,在此还可以同时处理一下A和B矩形边长相等的情况,只不过比边长相等的长度多了一两个键,不难发现,A的两个角上也可以分别连到B上。

  2. A的边长大于B的(B的两端都在A的\([x_1,x_2]\)区间中):产生氢键的数量处理方法与上面差不多,只不过不需要判断等于的情况了。

  3. A的下端点在B的区间内,但上端点不在(或者反之):产生氢键数量就是两个矩形交叉的部分以及边角处。

  4. A与B的矩阵的角相邻:直接加上1就好了。

当然,我们还需要一些优化,不难发现,对于矩阵的横坐标排序后满足单调性,如果j+1无法与i匹配那么j也不能,直接break。

code



#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,ans;
struct Ques
{
	int x,y,x2,y2;
}q[N];
bool comp(Ques x,Ques y)
{
	if(x.x==y.x)
		return x.y<x.y;
	return x.x<y.x;
}
#undef int
int main()
{
	#define int register long long
	#define ll long long
	scanf("%lld",&n);
	if(n==1)
	{
		int i=1;
		scanf("%lld%lld%lld%lld",&q[i].x,&q[i].y,&q[i].x2,&q[i].y2);
		ans+=(q[i].x2-q[i].x)*(q[i].y2-q[i].y)*2;
		printf("%lld",ans);
		return 0;
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%lld%lld%lld%lld",&q[i].x,&q[i].y,&q[i].x2,&q[i].y2);
		ans+=(q[i].x2-q[i].x)*(q[i].y2-q[i].y)*2;
	}
	sort(q+1,q+n+1,comp);
	for(int i=1;i<n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			if(q[j].x-q[i].x2>1)
				break;
			if(q[i].x-q[j].x2==1||q[j].x-q[i].x2==1)
			{
				if(q[i].y2<=q[j].y2&&q[i].y>=q[j].y)
				{
					ans+=2*(q[i].y2-q[i].y);
					if(q[i].y>q[j].y)
						ans++;
					if(q[i].y2<q[j].y2)
						ans++;
				}
				else if(q[j].y2<q[i].y2&&q[j].y>q[i].y)
				{
					ans+=2*(q[j].y2-q[j].y)+2;
				}
				else if(q[i].y2>=q[j].y&&q[i].y2<=q[j].y2)
				{
					ans+=2*(q[i].y2-q[j].y);
					if(q[i].y<q[j].y)
						ans++;
					if(q[i].y2<q[j].y2)
						ans++;
				}
				else if(q[i].y<=q[j].y2&&q[i].y>=q[j].y)
				{
					ans+=2*(q[j].y2-q[i].y);
					if(q[i].y>q[j].y)
						ans++;
					if(q[i].y2>q[j].y2)
						ans++;
				}
			}
			if(q[i].y-q[j].y2==1||q[j].y-q[i].y2==1)
			{
				if(q[i].x2<=q[j].x2&&q[i].x>=q[j].x)
				{
					ans+=2*(q[i].x2-q[i].x);
					if(q[i].x>q[j].x)
						ans++;
					if(q[i].x2<q[j].x2)
						ans++;
				}
				else if(q[j].x2<q[i].x2&&q[j].x>q[i].x)
				{
					ans+=2*(q[j].x2-q[j].x)+2;
				}
				else if(q[i].x2>=q[j].x&&q[i].x2<=q[j].x2)
				{
					ans+=2*(q[i].x2-q[j].x);
					if(q[i].x<q[j].x)
						ans++;
					if(q[i].x2<q[j].x2)
						ans++;
				}
				else if(q[i].x<=q[j].x2&&q[i].x>=q[j].x)
				{
					ans+=2*(q[j].x2-q[i].x);
					if(q[i].x>q[j].x)
						ans++;
					if(q[i].x2>q[j].x2)
						ans++;
				}
			}
			if((q[i].x-q[j].x2==1||q[j].x-q[i].x2==1)&&(q[i].y-q[j].y2==1||q[j].y2-q[i].y==1))
				ans++;
		}
	}
	printf("%lld",ans);
	return 0;
}

T2 模板

前言

考试的时候我也不知道咋回事,看着看着就看成小球的个数了。。。喜提0pts。

解题思路

线段树合并,启发式合并(说实话,我感觉和线段树合并没啥关系),有亿点难打,当然强者都去拿平衡树左右旋了,我这。。。

建一棵权值线段树,以时间为权值,在树上分别储存颜色的种类数个数

因为颜色会有负数所以我们需要离散化一下。并且将颜色与时间用pair封装压入vector数组(防止MLE)。

先进行一边dfs,求出各个节点的重儿子记为son[x]。

void dfs1(int x,int fa)
{
	siz[x]=v[x].size();
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa)
			continue;
		dfs1(to,x);
		siz[x]+=siz[to];
		if(siz[son[x]]<siz[to])
			son[x]=to;
	}
}

然后再进行dfs,以dfs的顺序来更新,先遍历子树进行处理,再遍历完之后清空处重儿子子节点的树以便重复利用空间防止对于以后的计算造成影响。对于重儿子的数据我们要重复利用。

然后先将现在的节点自身加入到线段树里,然后再将所有子节点也加入到线段树里(除重儿子之外的,毕竟重儿子的数值已经在树里了)

再以s[x](小桶最大装载量)为限制进行查找询问。最后将自己儿子的所有操作压入自己的vector数组便于自己的爸爸节点处理。

void dfs2(int x,int fa)
{
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa||to==son[x])
			continue;
		dfs2(to,x);
		clear(to);
	}
	if(son[x])
		dfs2(son[x],x);
	add(x);
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa||to==son[x])
			continue;
		add(to);
	}
	ans[x]=ask(1,1,m,s[x]);
	if(son[x])
	{
		move(x,son[x]);
		swap(v[x],v[son[x]]);
		for(int i=head[x];i;i=nxt[i])
		{
			int to=ver[i];
			if(to==fa)
				continue;
			move(to,x);
		}
	}
}

对于更新,laz标记下放以及查找这一类线段树基本操作在此不做过多说明,主要讲一下将节点的数值存入线段树的操作:

用一个tim数组储存每个颜色之前出现的最晚时间,然后对于以下两种情况分别进行处理:

  1. tim[clo]为零:也就是说该颜色从未出现过,我们增加这种颜色下表为tim

  2. 有这种颜色并且最晚大于当前要增加的时间:先将最晚的那个时间种类减去,然后加上现在的种类。

最后对于以上两种特殊情况以及最普通的出现且时间早于当前时间的情况一起将数量加一。

void add(int x)
{
	for(int i=0;i<v[x].size();i++)
	{
		int col=v[x][i].first,time=v[x][i].second;
		if(!tim[col])
		{
			update(1,1,m,time,1,0);
			tim[col]=time;
		}
		else if(time<tim[col])
		{
			update(1,1,m,tim[col],-1,0);
			update(1,1,m,time,1,0);
			tim[col]=time;
		}
		update(1,1,m,time,0,1);
	}
}

讲解到此结束,主要还是考验码力QAQ,是我太菜了。。。

code

#include<bits/stdc++.h>
#define ls x<<1
#define rs x<<1|1
using namespace std;
const int N=1e6+10;
int n,m,cnt,Q,s[N],tas[N],clo[N],ans[N];
int ys[N],siz[N],son[N<<2],tre[N<<2],tim[N],laz[N<<2];
int edg_tot,head[N],ver[N<<1],nxt[N<<1];
vector<pair<int,int > > v[N];// color time
void add_edge(int x,int y)
{
	ver[++edg_tot]=y;
	nxt[edg_tot]=head[x];
	head[x]=edg_tot;
}
void dfs1(int x,int fa)
{
	siz[x]=v[x].size();
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa)
			continue;
		dfs1(to,x);
		siz[x]+=siz[to];
		if(siz[son[x]]<siz[to])
			son[x]=to;
	}
}
void clear(int x)
{
	tre[1]=siz[1]=0;
	laz[1]=1;
	for(int i=0;i<v[x].size();i++)
		tim[v[x][i].first]=0;
}
void push_down(int x)
{
	if(!laz[x])
		return ;
	tre[ls]=tre[rs]=siz[ls]=siz[rs]=laz[x]=0;
	laz[ls]=laz[rs]=1;
}
void push_up(int x)
{
	tre[x]=tre[ls]+tre[rs];
	siz[x]=siz[ls]+siz[rs];
}
void update(int x,int l,int r,int pos,int val,int size)
{
	siz[x]+=size;
	tre[x]+=val;
	if(l==r)
		return ;
	push_down(x);
	int mid=(l+r)>>1;
	if(pos<=mid)
		update(ls,l,mid,pos,val,size);
	else
		update(rs,mid+1,r,pos,val,size);
	push_up(x);
}
void add(int x)
{
	for(int i=0;i<v[x].size();i++)
	{
		int col=v[x][i].first,time=v[x][i].second;
		if(!tim[col])
		{
			update(1,1,m,time,1,0);
			tim[col]=time;
		}
		else if(time<tim[col])
		{
			update(1,1,m,tim[col],-1,0);
			update(1,1,m,time,1,0);
			tim[col]=time;
		}
		update(1,1,m,time,0,1);
	}
}
void move(int x,int y)
{
	for(int i=0;i<v[x].size();i++)
		v[y].push_back(v[x][i]);
	v[x].clear();
}
int ask(int x,int l,int r,int rank)
{
	if(rank<=0)
		return 0;
	if(l==r)
		return tre[x];
	push_down(x);
	int mid=(l+r)>>1;
	if(rank>=siz[ls])
		return tre[ls]+ask(rs,mid+1,r,rank-siz[ls]);
	return ask(ls,l,mid,rank);
}
void dfs2(int x,int fa)
{
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa||to==son[x])
			continue;
		dfs2(to,x);
		clear(to);
	}
	if(son[x])
		dfs2(son[x],x);
	add(x);
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa||to==son[x])
			continue;
		add(to);
	}
	ans[x]=ask(1,1,m,s[x]);
	if(son[x])
	{
		move(x,son[x]);
		swap(v[x],v[son[x]]);
		for(int i=head[x];i;i=nxt[i])
		{
			int to=ver[i];
			if(to==fa)
				continue;
			move(to,x);
		}
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1,x,y;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add_edge(x,y);
		add_edge(y,x);
	}
	for(int i=1;i<=n;i++)
		scanf("%d",&s[i]);
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&tas[i],&clo[i]);
		ys[i]=clo[i];
	}
	sort(ys+1,ys+m+1);
	cnt=unique(ys+1,ys+m+1)-ys-1;
	for(int i=1;i<=m;i++)
		clo[i]=lower_bound(ys+1,ys+cnt+1,clo[i])-ys;
	for(int i=1;i<=m;i++)
		v[tas[i]].push_back(make_pair(clo[i],i));
	dfs1(1,0);
	memset(siz,0,sizeof(siz));
	dfs2(1,0);
	scanf("%d",&Q);
	for(int i=1,x;i<=Q;i++)
	{
		scanf("%d",&x);
		printf("%d\n",ans[x]);
	}
	return 0;
}

T3 大佬

解题思路

首先这是一道和期望没关系的期望题,正解比较麻烦 (反正我看不懂),这里说一种比较通俗易懂的方法:

我们先考虑k天之中的情况,对于k天总方案数显然是\(m^k\)对于不同的难度不难得出以下的式子:

\(\sum\limits_{i=1}^{m}(i^k-(i-1)^k) \times wt_i\)

最难的是i的方案有\(i^k\)种,但是其中有\((i-1)^k\)种情况是不做贡献的,因为有\((i-1)^k\)种情况选不到i,以i=2,k=2为例

情况有\((1,2)(2,2)(1,1)(2,1)\)但是有\((1,1)\)这一种情况是不做贡献的,因此上式正确。

最后我们再给运算结果乘天数就好了。

code



#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=510,mod=1000000007;
int n,m,k,ans,base,day,s[N];
int ksm(int x,int y)
{
	int sum=1;
	while(y)
	{
		if(y&1)
			sum=sum*x%mod;
		y>>=1;
		x=x*x%mod;
	}
	return sum;
}
#undef int
int main()
{
	#define int register long long
	#define ll long long
	scanf("%lld%lld%lld",&n,&m,&k);
	if(k>n)
	{
		printf("0");
		return 0;
	}
	day=n-k+1;
	for(int i=1;i<=m;i++)
		scanf("%lld",&s[i]);
	base=ksm(ksm(m,k),mod-2);
	for(int i=1;i<=m;i++)
	{
		int temp1=ksm(i,k),temp2=ksm(i-1,k);
		ans=(ans+(temp1-temp2+mod)%mod*s[i]%mod+mod)%mod;
	}
	printf("%lld",ans*day%mod*base%mod);
	return 0;
}

T4 宝藏

解题思路

首先正解是状压,但是状压+DFS好像也能过而且及其容易理解。

首先要确定的一点是我们要DFS的状态而并非节点,设当前深度为dis[i],f[i]为i状态最小花费,len[i][j]表示i与j之间道路的距离。不难得出以下式子:

\(f[s|(1<<j-1)]=f[s]+len[i][j] \times dis[j]\)

前提是\(!(s\&(1<<j-1))\)并且\(s\&(1<<i-1)\)因为如果j进去过了就没有必要再进一遍,而从i向j打通道就一定要满足i已经被打通。

因为一开始是随机选,因此我们暴力枚举没一个节点作为一开始的节点并向下进行深搜

当然上面的这一种打法是有问题的,因为遍历的深度不同导致了后效性无法处理的问题,我们可以在f数组上面加上两维,令 \(f[x][s][dep]\)表示状态为s并且节点x深度为dep的最小花费。实现过程也与上面的大同小异。

但是我 (懒得) 不屑于码了,就给出最一开始打法的代码吧。。。

code

#include<bits/stdc++.h>
//#define int long long
using namespace std;
const int N=15,M=1e3,base=0x3f3f3f3f;
int n,m,ans=1e9+7,dis[M],len[M+5][M+5],f[1<<N];
void dfs(int s)
{
//	cout<<s<<endl;
	for(int i=1;i<=n;i++)
		if((1<<(i-1))&s)
			for(int j=1;j<=n;j++)
				if(!((1<<(j-1))&s)&&len[i][j]!=0x3f3f3f3f)
//				cout<<i<<' '<<j<<endl;
					if(f[(1<<(j-1))|s]>f[s]+len[i][j]*dis[i])
					{
						int old=dis[j];
						dis[j]=dis[i]+1;
						f[(1<<(j-1))|s]=f[s]+len[i][j]*dis[i];
						dfs((1<<(j-1))|s);
						dis[j]=old;
					}
}
//#undef int
int main()
{
//	#define int register long long
//	#define ll long long
	scanf("%d%d",&n,&m);
	memset(len,0x3f,sizeof(len));
//	cout<<len[0][0]<<endl;
	for(int i=1,x,y,val;i<=m;i++)
	{
		scanf("%d%d%d",&x,&y,&val);
		len[x][y]=len[y][x]=min(len[x][y],val);
	}
	/*for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				len[i][j]=min(len[i][j],len[i][k]+len[k][j]);*/
	for(int i=1;i<=n;i++)
	{
		memset(dis,0x3f,sizeof(dis));
		memset(f,0x3f,sizeof(f));
		dis[i]=1;
		f[1<<(i-1)]=0;
		dfs(1<<(i-1));
		ans=min(ans,f[(1<<n)-1]);
	}
	printf("%d",ans);
	return 0;
}
posted @ 2021-06-11 06:36  Varuxn  阅读(82)  评论(0编辑  收藏  举报