AtCoder Beginner Contest 267

开学了,比较忙,一周补不完一场比赛。题意懒得描述了,写一下简要题解吧。Ex多项式,不会,这里只有 A-G 的题解。

A

对于周一到周五每一天分别输出答案。

B

模拟,注意一些细节即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
int ok[20],line[20];
signed main()
{
	for(int i=1;i<=10;++i)
	{
		char ch;cin>>ch;
		if(ch=='1')ok[i]=1;
		else ok[i]=0;
	}
	line[1]=ok[7];
	line[2]=ok[4];
	line[3]=ok[2]+ok[8];
	line[4]=ok[1]+ok[5];
	line[5]=ok[3]+ok[9];
	line[6]=ok[6];
	line[7]=ok[10];
	if(ok[1]==1)
	{
		cout<<"No";
		return 0;
	}
	for(int i=1;i<=7;++i)
	{
		for(int j=i+1;j<=7;++j)
		{
			if(!line[i]||!line[j])continue;
			int ok=0;
			for(int k=i+1;k<j;++k)
				if(line[k]==0)ok=1;
			if(ok)
			{
				cout<<"Yes";
				return 0;
			}
		}
	}
	cout<<"No";
	return 0;
}

C

前缀和优化。
考虑 \(f_i\) 表示以 \(i\) 开始,长度为 \(m\) 的区间的答案,考虑 \(f_i\) 如何由 \(f_{i-1}\) 推得。

\[f_i=f_{i-1}-sum_{i+m-2}+sum_{i-2}+a_i\times m \]

其中 \(sum\) 为前缀和。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
int n,m,a[N],s[N],f[N],ans; 
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	{
		cin>>a[i];
		s[i]=s[i-1]+a[i];
	}
	for(int i=1;i<=m;++i)
		f[1]+=i*a[i];
	ans=f[1];
	for(int i=2;i+m-1<=n;++i)
	{
		f[i]=f[i-1]-s[i+m-2]+s[i-2]+a[i+m-1]*m;
		ans=max(f[i],ans);
	}
	cout<<ans;
	return 0;
}

D

考虑 dp,设 \(f[i][j]\) 表示前 \(i\) 个数中取 \(j\) 个数的最大价值,转移时枚举这一位填什么。

\[f[i][j]=\max\{f[i-1][j],f[i-1][j-1]+a[i]\times j\} \]

注意初始化。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2005,inf=1e16;
int n,m,a[N],f[N][N];
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)cin>>a[i];
	for(int i=0;i<=n;++i)
		for(int j=0;j<=n;++j)
			f[i][j]=-inf;
	f[0][0]=0;
	for(int i=1;i<=n;++i)
		for(int j=0;j<=i;++j)
			if(j==0)f[i][j]=f[i-1][j];
			else f[i][j]=max(f[i-1][j-1]+j*a[i],f[i-1][j]);
	cout<<f[n][m];
	return 0;
}

E

题目是让求最大值最小,想到二分。
考虑如何判断?
贪心即可,每次对周围节点点权和小于 \(mid\) 的点暴力删除即可。
复杂度 \(\mathcal O((n+m)\log\sum a)\)

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
int n,m,a[N],sz[N],vis[N],szp[N];
vector<int>g[N];
inline bool check(int x)
{
	queue<int>q;
	for(int i=1;i<=n;++i)vis[i]=0,szp[i]=sz[i];
	for(int i=1;i<=n;++i)if(szp[i]<=x)q.push(i);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		if(vis[u])continue;vis[u]=1;
		for(int i=0;i<g[u].size();++i)
		{
			int v=g[u][i];szp[v]-=a[u];
			if(szp[v]<=x&&!vis[v])q.push(v);
		} 
	}
	int fl=1;
	for(int i=1;i<=n;++i)if(!vis[i]){fl=0;break;}
	return fl;
}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)cin>>a[i];
	for(int i=1;i<=m;++i)
	{
		int u,v;cin>>u>>v;
		g[u].push_back(v);g[v].push_back(u);
		sz[u]+=a[v];sz[v]+=a[u];
	}
	int l=0,r=1e15;
	while(l<r)
	{
		int mid=(l+r)/2;
		if(check(mid))r=mid;
		else l=mid+1;
	}
	cout<<l;
	return 0;
}

F

结论:离当前节点最远的点一定是树的直径的两端点之一。
所以可以求出是的直径的两个端点,分别 dfs,用类似 lca 的套路求当前节点的 \(k\) 级祖先。
证明咕咕咕。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,q,st,ed,dis[N],depst[N],deped[N],fst[N][20],fed[N][20];
vector<int>g[N];
void fw(int u,int fa)
{
	for(int i=0;i<g[u].size();++i)
	{
		int v=g[u][i];if(v==fa)continue;
		dis[v]=dis[u]+1;fw(v,u);
	}
}
void dfs(int u,int fa,int type)
{
	if(type==1)
	{
		fst[u][0]=fa;
		for(int i=1;i<=18;++i)
			fst[u][i]=fst[fst[u][i-1]][i-1];
	}
	else 
	{
		fed[u][0]=fa;
		for(int i=1;i<=18;++i)
			fed[u][i]=fed[fed[u][i-1]][i-1];
	}
	for(int i=0;i<g[u].size();++i)
	{
		int v=g[u][i];if(v==fa)continue;
		if(type==1)depst[v]=depst[u]+1;
		else deped[v]=deped[u]+1;
		dfs(v,u,type);
	}
}
signed main()
{
	cin>>n;
	for(int i=1;i<n;++i)
	{
		int u,v;cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	fw(1,1);
	int dmax=0;
	for(int i=1;i<=n;++i)
		if(dis[i]>dmax)dmax=dis[i],st=i;
	memset(dis,0,sizeof(dis));fw(st,st);
	dmax=0;
	for(int i=1;i<=n;++i)
		if(dis[i]>dmax)dmax=dis[i],ed=i;
	dfs(st,st,1);dfs(ed,ed,2);
	cin>>q;
	while(q--)
	{
		int u,k;cin>>u>>k;
		if(depst[u]<k&&deped[u]<k)cout<<-1<<endl;
		else if(depst[u]>=k)
			{
				for(int i=18;i>=0;--i)
					if(k>=(1<<i))k-=(1<<i),u=fst[u][i];
				cout<<u<<endl;	
			} 
			else
			{
				for(int i=18;i>=0;--i)
					if(k>=(1<<i))k-=(1<<i),u=fed[u][i];
				cout<<u<<endl;
			}
	}
	return 0;
}

G

给定一个数组 \(a\),对其重新排序,求排序后 \(a_i<a_{i+1}\) 的数量为 \(k\) 的方案数。

参考了这个讲解视频:https://www.bilibili.com/video/BV1BP4y1o7A4
考虑先将数组排序,然后从小到大依次插入序列。
这样做可以让当前插入的数大于等于之前所有的数。
考虑 dp,令 \(f[i][j]\) 表示现在已经插入了 \(i\) 个数,共有 \(j\) 个位置 \(a_i<a_{i+1}\) 的方案数。
那么怎么转移呢?

  • \(3\) \(5\) \(4\) 这种情况在插入 \(5\) 前后对数没有改变。
  • \(4\) \(5\) \(3\) 这种情况本来对数是 \(0\),插入 \(5\) 之后对数增加了 \(1\)

通过上面的几组数据可以发现,每插入一个数,答案对数至多 \(+1\)
我们再仔细考虑一下,假设当前插入的数为 \(x\),发现只有当 \(x>a_i\ge a_{i+1}\) 时答案对数才会增加,这样转移就容易多了。

\[f[i][j]=\begin{cases}f[i-1][j]&\rm{cnt[x]+j}\\f[i-1][j-1]&\rm{else}\end{cases} \]

其中 \(\rm cnt[x]\) 表示 \(x\) 的出现次数。

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e3+5;
const int mod=998244353;
int n,k,a[N],cnt[N],f[N][N];
signed main()
{
	cin>>n>>k;
	for(int i=1;i<=n;++i)cin>>a[i];
	sort(a+1,a+n+1);f[0][0]=1;
	for(int i=1;i<=n;++i)
	{
		for(int j=0;j<=min(i,k+1);++j)
		{
			f[i][j]=(f[i][j]+f[i-1][j]*(j+cnt[a[i]])%mod)%mod;
			if(j)f[i][j]=(f[i][j]+f[i-1][j-1]*(i-j-cnt[a[i]]+1)%mod)%mod;
		}
		cnt[a[i]]++;
	}
	cout<<f[n][k+1];
	return 0;
} 
posted @ 2022-09-08 22:36  lnwhl  阅读(36)  评论(0编辑  收藏  举报