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}\) 推得。
其中 \(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\) 个数的最大价值,转移时枚举这一位填什么。
注意初始化。
点击查看代码
#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}\) 时答案对数才会增加,这样转移就容易多了。
其中 \(\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;
}