8.18考试总结(NOIP模拟43)[第一题·第二题·第三题·第四题]

愿你和重要的人,在来日重逢。

前言

题目名字起的很随意。。。

这天 Luogu 的运势好像是大凶(忌:打模拟赛,注意报零)。

但是考得还不错,拿到了这么多场模拟赛以来第二三个场上AC。

所以说,我爱大凶

T1 第一题

解题思路

官方题解好像不干人事,直接咕了。。

其实做法都差不多,都是乱搞(反正我是这么干的)。

对于整棵树上的每一个点开一个 multiset,用来维护子树内剩余的每一个士兵的深度。

然后每一次进行子树合并的时候选择深度最小的,但是到当前子树根节点距离的两倍小于根节点深度的点进行利用。

可以这么理解:把整个过程视作最后停留的节点的深度加上其它经过节点到 该节点与停留节点的LCA的距离的二倍(毕竟要下去再上来嘛)

然后就再开一个数组记录上面有来回的时间的贡献。

时间复杂度我不会算,好像是 n 的,但是我的 set 合并的时候都没有启发式合并,感觉最坏的情况会被搞成接近 \(n^2\)

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=1e5+10,M=1e3+10;
int n,ans,fa[N],f[N],dep[N],mx[N];
vector<int> v[N];
multiset<int> s[N];
void add_edge(int x,int y)
{
	v[x].push_back(y);
}
void dfs(int x)
{
	mx[x]=dep[x];
	for(int i=0;i<v[x].size();i++)
	{
		int to=v[x][i];
		if(to==fa[x])	continue;
		fa[to]=x;
		dep[to]=dep[x]+1;
		dfs(to);
		mx[x]=max(mx[x],mx[to]);
	}
}
void merge(multiset<int> &a,multiset<int> &b)
{
	for(auto it=b.begin();it!=b.end();it++)
		a.insert((*it)+1);
}
bool comp(int x,int y)
{
	return mx[x]<mx[y];
}
void solve(int x)
{
	bool flag=false;
	sort(v[x].begin(),v[x].end(),comp);
	for(int i=0;i<v[x].size();i++)
	{
		int to=v[x][i];
		if(to==fa[x])	continue;
		flag=true;	solve(to);
		for(auto it=s[x].begin();it!=s[x].end();it++)
		{
			if((*it)>=dep[x]||!s[to].size())	break;
			auto it2=(--s[to].end());
			if((*it2)+1<(*it))	break;
			f[x]+=(*it)*2;
			s[to].erase(it2);
			s[x].erase(it);
			s[x].insert((*it2)+1);
		}
		f[x]+=f[to];
		merge(s[x],s[to]);
	}
	if(!flag)	s[x].insert(0);
}
signed main()
{
	n=read();
	for(int i=1,x,y;i<n;i++)
	{
		x=read();	y=read();
		add_edge(x,y);
		add_edge(y,x);
	}
	dfs(1);
	solve(1);
	for(auto it=s[1].begin();it!=s[1].end();it++)
		ans+=(*it);
	printf("%lld",ans+f[1]);
	return 0;
}

T2 第二题

解题思路

做法依旧非常暴力。(但是好像别的做法也可以被极端数据卡掉,比如n=1,m=1e5

一眼看出是二分答案。

二分最小的差值,判断是否可以用 \(\le k\) 的步数实现。

用一种类似于 SPFA 的方法实现判断。

为了保证每一个点都被扫到,先把所有的点都放到队列里面。

然后扫描每一个点周围的点的最大值,看看差值是否符合条件,不符合的话就更改并计入贡献。

接下来再次扫描周围的六个点,对于不合法的更改计入贡献,对于更改过但是不再队列里的入队。

最后把贡献和与 k 比较就好了。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=5e3+10,M=1e5+10,INF=1e18;
int n,m,K,minn=INF,maxn;
int d1[10]={0,0,0,1,-1,1,-1};
int d2[10]={0,1,-1,0,0,-1,1};
bool vis[M];
vector<int> s[M],p[M];
pair<int,int> id;
struct Queue
{
	int l,r;
	pair<int,int> num[M<<2];
	bool empty(){return l>r;}
	pair<int,int> front(){return num[l];}
	void clear(){l=1;r=0;}
	void push(pair<int,int> temp){num[++r]=temp;}
	void pop(){l++;};
}q;
int pos(int x,int y)
{	
	return (x-1)*m+y;
}
bool check(int mid)
{
	memset(vis,true,sizeof(vis));
	int sum=0;	q.clear();
	for(int i=1;i<=n;i++)
	{
		p[i]=s[i];
		for(int j=1;j<=m;j++)
			q.push(make_pair(i,j));
	}
	while(!q.empty())
	{
		int x=q.front().first,y=q.front().second;
		q.pop();	vis[pos(x,y)]=false;
		int maxn=0;
		for(int i=1;i<=6;i++)
		{
			int x2=x+d1[i],y2=y+d2[i];
			if(x2<=0||y2<=0||x2>n||y2>m)	continue;
			maxn=max(maxn,p[x2][y2]);
		}
		if(maxn-p[x][y]>mid)
		{
			sum+=maxn-p[x][y]-mid;
			p[x][y]=maxn-mid;
		}
		if(sum>K)	return false;
		for(int i=1;i<=6;i++)
		{
			int x2=x+d1[i],y2=y+d2[i];
			if(x2<=0||y2<=0||x2>n||y2>m)	continue;
			if(abs(p[x][y]-p[x2][y2])>mid)
			{
				sum+=p[x][y]-p[x2][y2]-mid;
				p[x2][y2]=p[x][y]-mid;
				if(!vis[pos(x2,y2)])	q.push(make_pair(x2,y2)),vis[pos(x2,y2)]=true;
			}
		}
		if(sum>K)	return false;
	}
	return sum<=K;
}
signed main()
{
	n=read();	m=read();	K=read();
	for(int i=1;i<=n;i++)	s[i].push_back(0);
	for(int i=1;i<=n;i++)
		for(int j=1,x;j<=m;j++)
		{
			x=read();
			s[i].push_back(x);
			minn=min(minn,s[i][j]);
			if(maxn<=s[i][j])	id=make_pair(i,j);
			maxn=max(maxn,s[i][j]);
		}
	int l=0,r=maxn-minn,ans=maxn-minn;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid)){r=mid-1;ans=mid;}
		else	l=mid+1;
	}
	printf("%lld",ans);
	return 0;
}

T3 第三题

解题思路

看出来是数位 DP 了,奈何我太弱,不会。。

\(f_{i,j,l,r}\) 表示当先考虑到第 i 位,已经填上了 j 个 1,是否压紧上下界。

储存两个值:当前状态的数字个数,以及所有数字的总和。

为了方便我们计算 a 到 INF 以及 b 到 INF 的值,最后减一下就好了。

先记忆化DFS一遍算出限制内合法的个数。

然后就类似于查询排名,根据第一维的数字个数求出合法的数字总和。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int Lg=30;
struct Node
{
	int x,y;
}f[Lg+10][Lg+10][2][2];
int T,li,ri,a,b;
Node dfs_unlim(int i,int j,bool l,bool r)
{
	if(i<0||j<0)	return (Node){0ll,0ll};
	if(~f[i][j][l][r].x)	return f[i][j][l][r];
	int lim1=l&(li>>i),lim2=r&(!((ri>>i)&1));
	Node ans1=(Node){0ll,0ll},ans2=(Node){0ll,0ll};
	if(!lim1)	ans1=dfs_unlim(i-1,j,(!((li>>i)&1))&l,(!((ri>>i)&1))&r);
	if(!lim2)	ans2=dfs_unlim(i-1,j-1,(li>>i)&l,(ri>>i)&r);
	return f[i][j][l][r]=(Node){ans1.x+ans2.x,ans1.y+ans2.y+ans2.x*(1<<i)};
}
Node dfs_lim(int i,int j,bool l,bool r,int rk)
{
	if(i<0||j<0||!rk)	return (Node){0ll,0ll};
	if(~f[i][j][l][r].x&&f[i][j][l][r].x<=rk)	return f[i][j][l][r];
	int lim1=l&(li>>i),lim2=r&(!((ri>>i)&1));
	Node ans1=(Node){0ll,0ll},ans2=(Node){0ll,0ll};
	if(!lim1)	ans1=dfs_lim(i-1,j,(!((li>>i)&1))&l,(!((ri>>i)&1))&r,rk);
	if(!lim2)	ans2=dfs_lim(i-1,j-1,(li>>i)&l,(ri>>i)&r,rk-ans1.x);
	return (Node){ans1.x+ans2.x,ans1.y+ans2.y+ans2.x*(1<<i)};
}
int work(int rk)
{
	int sum=0;
	for(int i=0;i<=Lg;i++)
		if(rk>=f[Lg][i][1][1].x)
		{
			if(!(~f[Lg][i][1][1].x))	continue;
			rk-=f[Lg][i][1][1].x;
			sum+=f[Lg][i][1][1].y;
		}
		else
		{
			sum+=dfs_lim(Lg,i,1,1,rk).y;
			break;
		}
	return sum;
}
int solve()
{
	li=read();	ri=read();	b=read();	a=read();
	a=ri-li+1-a+1;	b=ri-li+1-b+1;
	memset(f,-1,sizeof(f));
	f[0][0][0][0]=f[0][0][0][1]=(Node){1ll,0ll};
	f[0][0][1][0]=f[0][0][1][1]=(Node){!(li&1),0ll};
	f[0][1][0][0]=f[0][1][1][0]=(Node){1ll,1ll};
	f[0][1][0][1]=f[0][1][1][1]=(Node){ri&1,ri&1};
	for(int i=1;i<=Lg;i++)
		f[Lg][i][1][1]=dfs_unlim(Lg,i,1,1);
	return work(b)-work(a-1);
}
signed main()
{
	T=read();
	while(T--)	printf("%lld\n",solve());
	return 0;
}

T4 第四题

解题思路

题面描述的不太清楚,我说一下自己的理解。

对于一个序列,保证每一位都是在 \([1,n]\) 范围内的,并且前 i 位的最大值等于 前 i-1 位的最大值,或者只比它多 1 。(这个序列并不一定是 \(1\sim n\) 的排列)

\(f_{i,j}\) 表示选择 i 个数最大的数字为 j 的方案数。

\(g_{i,j}\) 表示在最大值为 j 的序列后再选择 i 个数的方案数。

这两个方程可以互补求出整个序列的值。

因为\(k^2=C_{k}^{2}\times 2 + k\),所以每一种方案的贡献是 1+后面再选一个 x 的方案数 \(\times 2\)

可以的得出在第 i 个位置的 x 的贡献就是:

\[\sum\limits_{y\ge x}f_{i-1,y}\times(g_{n-i,y}\times 2\times (n-i)\times g_{n-i-1,y}) \]

\[+f_{i-1,x-1}\times(g_{n-i,x}\times 2\times (n-i)\times g_{n-i-1,x}) \]

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=3e3+10;
int n,mod,f[N][N],g[N][N],s[N][N],ans[N];
void add(int &x,int y){x+=y;if(x>=mod)x-=mod;}
signed main()
{
	n=read();	mod=read();
	f[0][0]=f[1][1]=1;
	for(int i=1;i<=n;i++)
		g[0][i]=1;
	for(int i=2;i<=n;i++)
		for(int j=1;j<=i;j++)
			add(f[i][j],(f[i-1][j]*j%mod+f[i-1][j-1])%mod);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n-i;j++)
			add(g[i][j],(g[i-1][j]*j%mod+g[i-1][j+1])%mod);
	for(int i=1;i<=n;i++)
		for(int j=i;j>=1;j--)
			add(s[i][j],(f[i-1][j]*(g[n-i][j]+2*(n-i)*g[n-i-1][j]%mod)%mod+s[i][j+1])%mod);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			add(ans[j],(s[i][j]+f[i-1][j-1]*(g[n-i][j]+2*(n-i)*g[n-i-1][j]%mod))%mod);
	for(int i=1;i<=n;i++)
		printf("%lld ",(ans[i]+mod)%mod);
	return 0;
}

改题记录

posted @ 2021-08-20 10:38  Varuxn  阅读(116)  评论(0编辑  收藏  举报