牛客练习赛89 题解
前言
比赛链接:https://ac.nowcoder.com/acm/contest/11179#question
比赛开始后 \(1\) 小时才注意到。看了一下榜感觉过 \(4\) 题就能不掉,于是直接开了 D 题。发现会做,然后就打了。
结果第一次 AK 了练习赛。这不水一篇博客 2333。
A 牛牛吃米粒
题目链接:https://ac.nowcoder.com/acm/contest/11179/A
直接把 \(s\) 转化成二进制数就好了。
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
int n,k;
ull s,x;
int main()
{
cin>>n>>k>>s;
for (int i=1;i<=k;i++)
{
cin>>x;
if (x==64) continue;
if (s&(1ULL<<(x-1))) return printf("NO"),0;
}
printf("YES");
return 0;
}
B 牛牛嚯可乐
题目链接:https://ac.nowcoder.com/acm/contest/11179/B
长度很小。直接爆搜。可能有更简单的做法。这种无脑我就直接写了。
#include <bits/stdc++.h>
using namespace std;
const int N=10;
const char t[]={'\000','c','o','c','a','c','o','l','a'};
int ans;
char s[N];
void dfs(int x,int cnt)
{
if (x>8) { ans=min(ans,cnt); return; }
if (s[x]==t[x]) dfs(x+1,cnt);
else
{
for (int i=x+1;i<=8;i++)
if (s[i]==t[x])
{
swap(s[i],s[x]);
dfs(x+1,cnt+1);
swap(s[i],s[x]);
}
}
}
int main()
{
scanf("%s",s+1);
ans=1e9; dfs(1,0);
cout<<ans;
return 0;
}
C 牛牛吃豆人
题目链接:https://ac.nowcoder.com/acm/contest/11179/C
也就是找两条路径要求在起点终点外不交。
显然两条路径必然是 \((1,1)\to (2,1)\to \cdots\to (n,2)\to (n,3)\) 和 \((1,1)\to (1,2)\to \cdots\to (n-1,3)\to (n,3)\)。因为每一列至少有一个障碍,所以只需要看转折的时候会不会出问题。
也就是第一列最上的障碍必须比第二列最下的障碍至少低 \(2\) 行;第三列最下的障碍必须比第二列最上的障碍至少高两行。
#include <bits/stdc++.h>
using namespace std;
const int N=1000010;
int n,m,p1,p2,p3,p4;
int main()
{
scanf("%d%d",&n,&m);
p1=1e9; p2=1e9; p3=0; p4=0;
for (int i=1,x,y;i<=m;i++)
{
scanf("%d%d",&x,&y);
if (x==1) p1=min(p1,y);
if (x==2) p2=min(p2,y),p3=max(p3,y);
if (x==3) p4=max(p4,y);
}
if (p1>p3+1 && p4<p2-1) cout<<"YES";
else cout<<"NO";
return 0;
}
D 牛牛种小树
题目链接:https://ac.nowcoder.com/acm/contest/11179/D
首先除了根节点以外所有点都恰好有一个父亲,忽略掉这个度数后,只需要考虑每一个点往儿子的边的度数。
把根节点单独处理,就是将 \(n-1\) 个物品分为 \(n-1\) 堆,大小为 \(x\) 的一堆的价值为 \(f(x+1)\)。
这个直接完全背包搞就行了。但是注意如果不足 \(n-1\) 堆,需要凑大小为 \(0\) 的堆,而大小为 \(0\) 的堆的价值并不是 \(0\),所以考虑贡献提前计算,也就是一开始就设价值为 \((n-1)\times f(1)\),然后每加入一堆时贡献都需要减去 \(f(1)\)。
时间复杂度 \(O(n^2)\)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=10010;
int n;
ll ans,f[N],g[N],v[N];
int main()
{
scanf("%d",&n);
for (int i=1;i<n;i++)
scanf("%lld",&v[i]);
memset(f,0xcf,sizeof(f));
f[0]=v[1]*(n-1);
for (int i=1;i<n;i++)
for (int j=i;j<n;j++)
f[j]=max(f[j],f[j-i]+v[i+1]-v[1]);
for (int i=1;i<n;i++)
ans=max(ans,f[n-1-i]+v[i]);
cout<<ans;
return 0;
}
E 牛牛小数点
题目链接:https://ac.nowcoder.com/acm/contest/11179/E
手玩一下会发现,对于一个数 \(x=2^{a}\times 5^{b}\times p\):
- 如果 \(p=1\),那么这是一个有限小数,贡献为 \(0\)。
- 如果 \(p>1\),那么贡献为 \(\max(a,b)+1\)。
把每一次询问拆成两个前缀和相减,对于 \(p=1\) 的部分,可以最后暴力找出每一个符合的数减去贡献。所以可以忽略掉 \(p\) 的限制。
考虑贡献至少为 \(k\) 的数有几个。也就是质因数分解后,\(\max(a,b)+1\geq k\) 的数量。很显然是 \(\lfloor\frac{n}{2^k}\rfloor+\lfloor\frac{n}{5^k}\rfloor-\lfloor\frac{n}{10^k}\rfloor\)。
那么枚举 \(k\) 计算一下就好了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD=998244353;
int Q;
ll l,r;
ll solve(ll n)
{
ll ans=0;
ll res1=1,res2=1,res3=1;
for (int i=1;i<=50;i++)
{
if (res1<=n) ans=(ans+n/res1)%MOD,res1*=2LL;
if (res2<=n) ans=(ans+n/res2)%MOD,res2*=5LL;
if (res3<=n) ans=(ans-n/res3)%MOD,res3*=10LL;
}
res1=1;
for (int i=0;res1<=n;i++)
{
res2=res1;
for (int j=0;res2<=n;j++)
ans=(ans-max(i,j)-1)%MOD,res2=res2*5LL;
res1*=2LL;
}
return (ans%MOD+MOD)%MOD;
}
int main()
{
scanf("%d",&Q);
while (Q--)
{
scanf("%lld%lld",&l,&r);
cout<<(solve(r)-solve(l-1)+MOD)%MOD<<"\n";
}
return 0;
}
F 牛牛防疫情
题目链接:https://ac.nowcoder.com/acm/contest/11179/F
一眼看上去就觉得很网络流。
发现每一个格子要么被感染,要么被墙隔离。观察到如果两个格子之间有墙,当且仅当这两个格子一个是被感染,一个不被感染。
那么这个模型就十分的像最大权闭合子图了。源点连向每一个感染源,流量为 \(+\infty\);相邻格子之间连流量为 \(1\);所有格子向 \(T\) 连流量为 \(c\)。
这样如果一个格子最后不被感染,那么与相邻的被感染的格子之间的边就需要割掉;如果被感染,那么与 \(T\) 之间的边就需要割掉。
最后答案再减去初始 \(m\) 个感染源的代价即可。
#include <bits/stdc++.h>
using namespace std;
const int N=2000010,Inf=1e9;
const int dx[]={0,0,0,-1,1},dy[]={0,-1,1,0,0};
int n,m,c,S,T,maxf,tot=1,head[N],cur[N],dep[N];
int ID(int x,int y)
{
return n*(x-1)+y;
}
struct edge
{
int next,to,flow;
}e[N];
void add(int from,int to,int flow)
{
e[++tot]=(edge){head[from],to,flow};
head[from]=tot;
swap(to,from);
e[++tot]=(edge){head[from],to,0};
head[from]=tot;
}
bool bfs()
{
memset(dep,0x3f3f3f3f,sizeof(dep));
memcpy(cur,head,sizeof(head));
queue<int> q;
q.push(S); dep[S]=0;
while (q.size())
{
int u=q.front(); q.pop();
for (int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
if (e[i].flow && dep[v]>dep[u]+1)
{
dep[v]=dep[u]+1;
q.push(v);
}
}
}
return dep[T]<Inf;
}
int dfs(int x,int flow)
{
if (x==T) return flow;
int used=0,res;
for (int i=cur[x];~i;i=e[i].next)
{
int v=e[i].to; cur[x]=i;
if (e[i].flow && dep[v]==dep[x]+1)
{
res=dfs(v,min(flow-used,e[i].flow));
e[i].flow-=res; e[i^1].flow+=res; used+=res;
if (used==flow) return used;
}
}
return used;
}
void dinic()
{
while (bfs()) maxf+=dfs(S,Inf);
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d%d",&n,&m,&c);
S=N-1; T=N-2;
for (int i=1,x,y;i<=m;i++)
{
scanf("%d%d",&x,&y);
x++; y++;
add(S,ID(x,y),Inf);
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
{
add(ID(i,j),T,c);
for (int k=1;k<=4;k++)
{
int x=i+dx[k],y=j+dy[k];
if (x>=1 && x<=n && y>=1 && y<=n) add(ID(i,j),ID(x,y),1);
}
}
dinic();
cout<<maxf-c*m;
return 0;
}