美团 CodeM 资格赛 Round A 题解

初赛A轮题目在线练习地址

身体训练

  • 根据期望的线性性质,枚举第 i 个人是第 j 个轮到的,每个事件的概率都是 1/n。 相当于计算:
    图片说明
    暴力计算即可。 
  • 代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define dep(i,x,y) for(int i=x;i>=y;--i)
using namespace std;
const int N = 1005;
int n;
double v, u, c[N], d[N];
int main(){
    scanf("%d%lf%lf",&n,&v,&u);
    rep(i,1,n) scanf("%lf",c+i);
    rep(i,1,n) scanf("%lf",d+i);
    double ans = 0;
    rep(i,1,n) rep(j,1,n) ans+=1.0/(c[i]-(j-1)*d[i]-v);
    ans*=u;
    printf("%.3lf\n",ans);
}

合并回文子串

  • 考虑 c 中的回文子串,既然是子串,就一定可以拆成 a, b 两串的两个子串的 combine。不妨 设是 a[i, j]与 b[k, l]的 combine,则可以考虑动态规划的状态 f[i][j][k][l]表示 a[i, j]与 b[k, l]的 combine 能否组成回文子串。 则可以匹配第一个字符和最后一个字符来转移,根据第一个字符和最后一个字符分别来自 a 还是 b 共有四种转移:

  • 边界情况:
    1. 当 j – i + 1 + l – k + 1 = 0 时答案是 true
    2. 当 j – i + 1 + l – k + 1 = 1 时答案是 true。
  • 代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
int T;
int n,m;
char a[55],b[55];
bool f[55][55][55][55];
int main()
{
  for(scanf("%d",&T);T--;)
  {
    scanf("%s",a+1);n=strlen(a+1);
    scanf("%s",b+1);m=strlen(b+1);
    int ans=0;
    for(int d1=0;d1<=n;d1++)
      for(int d2=0;d2<=m;d2++)
        for(int i=1,j=d1;j<=n;i++,j++)
          for(int k=1,l=d2;l<=m;k++,l++)
          {
            if(d1+d2<=1)f[i][j][k][l]=1;
            else
            {
              f[i][j][k][l]=0;
              if(d1>1&&a[i]==a[j])f[i][j][k][l]|=f[i+1][j-1][k][l];
              if(d1&&d2&&a[i]==b[l])f[i][j][k][l]|=f[i+1][j][k][l-1];
              if(d1&&d2&&b[k]==a[j])f[i][j][k][l]|=f[i][j-1][k+1][l];
              if(d2>1&&b[k]==b[l])f[i][j][k][l]|=f[i][j][k+1][l-1];
            }
            if(f[i][j][k][l])ans=max(ans,d1+d2);
          }
    printf("%d\n",ans);
  }
  return 0;
}

倒水

大致分三种情况讨论:

  1. T 大于所有 ti:由于要求温度最大,当然是把所有水都倒完。
  2. T 小于等于所有 ti:因为倒水只会把水的温度往 T 靠拢,所以找一个最小的 ti,把其他所
    有 tj 都倒水变成 ti。
  3. 存在 ti < T 且存在 tj > T:显然无解。
    代码:
#include<bits/stdc++.h>
#define int64 long long
#define sqr(x) (x)*(x)
#define mk make_pair
#define pb push_back
#define fi first
#define se second
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define VI vector<int>
#define VI64 vector<int64>
#define VS vector<string>
#define PII pair<int,int>
#define PDD pair<double,double>
#define VPII vector< PII >
#define SZ(x) ((int)(x).size())
#define N 120000
using namespace std;
int n,t[N],c[N];
int T,C;
void P(double x){
	printf("Possible\n");
	printf("%.4lf\n",x);
	exit(0);
}
void fail(){
	printf("Impossible\n");
	exit(0);
}
int main(int argv,char **argc){
freopen(argc[1],"r",stdin);
freopen(argc[2],"w",stdout);
	scanf("%d",&n);
	scanf("%d%d",&T,&C);
	cerr<<C<<endl;
	bool f1=0,f2=0;
	rep(i,1,n){
		scanf("%d%d",&t[i],&c[i]);
		if(t[i]<T)f1=1;
		if(t[i]>T)f2=1;
	}
	if(f1 && f2)fail();
	if(!f1 && !f2)P(T);
	if(f1){
		T*=-1;
		rep(i,1,n)t[i]*=-1;
	}
	int minnT=1e9;
	int64 cc=0;
	rep(i,1,n)minnT=min(minnT,t[i]);
	if(minnT==T)fail();
	rep(i,1,n)cc+=(minnT*c[i]-c[i]*t[i]);
	if(T-minnT>0 && cc>(int64)C*(T-minnT))fail();
	if(T-minnT<0 && cc<(int64)C*(T-minnT))fail();
	cerr<<"lucky"<<endl;
	if(f1){
		double f1=0,f2=0;
		f1=(double)T*C;
		f2=C;
		rep(i,1,n)f1+=t[i]*c[i],f2+=c[i];
		P(-f1/f2);
	}else P(minnT*(f1?-1:1));
	return 0;
}

最长树链

  • 枚举每个约数,保留对应的边,做一次最长路径。因为一个数的约数个数可以保证,所以复杂度符合要求。
  • 代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <map>
#include <vector>
using namespace std;
int n, l, c[100001], len, value[100001], visit[100001], V[100001];
bool b[100001];
map< int, vector<int> > factor;
struct node{
  node *next;
  int where;
} a[200001], *first[100001];
inline void makelist(int x, int y) {
  a[++l].where = y;
  a[l].next = first[x];
  first[x] = &a[l];
}
pair<int, int> bfs(int init, int v, int round) {
  c[1] = init; visit[init] = 1;
  int pos = 0, will = 0;
  int k = 1, l = 1;
  for (; l <= k; ++l)
  {
    int m = c[l];
    if (visit[m] > will)
      will = visit[m], pos = m;
    for (node *x = first[m]; x; x = x->next)
      if (!(value[x->where] % v) && !visit[x->where])
      {
        visit[x->where] = visit[m] + 1;
        c[++k] = x->where;
      }
  }
  if (round == 0) 
    for (int i = 1; i <= k; i++)
      visit[c[i]] = 0;
  return make_pair(pos, will);
}
int calc(int v) {
  vector<int> idx = factor[v];
  int will = 0;
  for (int i = 0; i < idx.size(); i++)
    if (!visit[idx[i]])
    {
      will = max(will, bfs(bfs(idx[i], v, 0).first, v, 1).second);
    }
  for (int i = 0; i < idx.size(); i++)
    visit[idx[i]] = 0;
  return will;
}
     
int main() {
  len = 0;
  memset(b, false, sizeof(b));
  for (int i = 2; i <= 100000; i++)
  {
    if (!b[i])
      c[++len] = i;
    for (int j = 1; j <= len; ++j)
      if (c[j] * i > 100000)
        break;
      else
      {
        b[c[j] * i] = true;
        if (!(i % c[j]))
          break;
      }
  }
  scanf("%d", &n);
  memset(first, 0, sizeof(first)); l = 0;
  for (int i = 1; i < n; i++)
  {
    int x, y;
    scanf("%d%d", &x, &y);
    makelist(x, y);
    makelist(y, x);
  }
  factor.clear();
  for (int i = 1; i <= n; i++)
  {
    int x;
    scanf("%d", &x);
    value[i] = x;
    for (int j = 1; c[j] * c[j] <= x; ++j)
      if (!(x % c[j]))
      {
        if (factor.find(c[j]) == factor.end())
          factor[c[j]].clear();
        factor[c[j]].push_back(i);
        for (; !(x % c[j]); )
          x /= c[j];
      }
    if (x != 1)
    {
      if (factor.find(x) == factor.end())
        factor[x].clear();
      factor[x].push_back(i);
    }
  }
  int ans = 0;
  memset(visit, 0, sizeof(visit));
  memset(V, 0, sizeof(V));
  for (map< int, vector<int> >::iterator itr = factor.begin(); itr != factor.end(); ++itr)
    ans = max(ans, calc(itr->first));
  printf("%d\n", ans);
}

数列互质

  • 对于在整个序列中出现次数超过 sqrt(n)的数,最多只有 sqrt(n)种,那么对于这些数,很容易用一个数组做前缀和维护,可以在每次询问中直接求出它们的出现次数并 gcd 判断。剩下的数的出现次数不超过 sqrt(n),那么可以对它出现恰好 w 次的区间左右端点写出一个范围约束,这样的约束只总共只有 nsqrt(n)对,把左端点和右端点看成笛卡尔坐标系上的 x,y坐标,约束就可以看成一个矩形,问题转化为求询问点被多少矩形覆盖到,可以用扫描线来离线对所有询问计算。
  • 一个 trick case,k=1 是需要注意 gcd(1,0)=1 的问题。
  • 代码:
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <math.h>
#include <algorithm>
#define L 100
using namespace std;
int n,m,S,Sn,i,j,k,w,l,r,u,v,ul,vr,c;
int a[50005],sum[50000/L+5][50005],ans[50005],t[50005];
int ql[50005],qr[50005],qk[50005];
vector<int> V[100005];
vector<pair<int,int> > Ins[L+5][50005],Del[L+5][50005];
vector<int> query[50005];
int main()
{
  scanf("%d%d",&n,&m);S=min((int)sqrt(n),100);
  for(i=1;i<=n;++i)scanf("%d",&a[i]),V[a[i]].push_back(i);
  
  for(i=1;i<=n;++i)
  if(V[i].size()>=S)
  {
    ++Sn;
    for(k=V[i].size(),j=0;j<k;++j)++sum[Sn][V[i][j]];
    for(j=1;j<=n;++j)sum[Sn][j]+=sum[Sn][j-1];
  }
  else
  {
    c=V[i].size();
    for(j=0;j<c;++j)
    {
      u=V[i][j];
      if(j)ul=V[i][j-1]+1;else ul=1;
      for(k=j,l=1;k<c;++k,++l)
      {
        v=V[i][k];
        if(k<c-1)vr=V[i][k+1]-1;else vr=n;
        Ins[l][ul].push_back(make_pair(v,vr));
        Del[l][u].push_back(make_pair(v,vr));
      }
    }
  }
  
  for(i=1;i<=m;++i)
  {
    scanf("%d%d%d",&ql[i],&qr[i],&qk[i]);
    l=ql[i];r=qr[i];
    query[l].push_back(i);
    for(j=1;j<=Sn;++j)if(sum[j][r]-sum[j][l-1]>0&&__gcd(sum[j][r]-sum[j][l-1],qk[i])==1)++ans[i];
  }
  
  for(i=1;i<S;++i)
  for(j=1;j<=n;++j)
  {
    for(k=Ins[i][j].size()-1;k>=0;--k)
    {
      l=Ins[i][j][k].first;
      r=Ins[i][j][k].second;
      for(w=l;w<=n;w+=w&-w)++t[w];
      for(w=r+1;w<=n;w+=w&-w)--t[w];
    }
    for(k=query[j].size()-1;k>=0;--k)
    {
      l=query[j][k];
      u=qr[l];v=qk[l];
      if(__gcd(v,i)!=1)continue;
      for(w=u;w;w-=w&-w)ans[l]+=t[w];
    }
    for(k=Del[i][j].size()-1;k>=0;--k)
    {
      l=Del[i][j][k].first;
      r=Del[i][j][k].second;
      for(w=l;w<=n;w+=w&-w)--t[w];
      for(w=r+1;w<=n;w+=w&-w)++t[w];
    }
  }
  
  for(i=1;i<=m;++i)printf("%d\n",ans[i]);
}

二分图染色

  • 完全二分图转棋盘模型,即 N ×N 棋盘上放黑白棋子,每个格子至多放一个,同行同列没有相同颜色的棋子。
  • 用容斥原理,先假设每个格子可以放多个,再减去不合法的方案。如果每个格子可以放多个,一个不合法的格子所在的行和列都不会再摆放其他棋子,也没有两个不合法的格子同行同列。因此我们有计算式:
    图片说明
  • 其中 AN−i 表示在 (N − i) × (N − i)的棋盘上放单色棋子,使得没有棋子同行同列的方案数。 考虑从 (N − 1) ×(N − 1)棋盘递推到 N ×N 棋盘,我们有:
    图片说明
  • 第一项是在第 N 行或者第 N 列上放一枚棋子或者不放的方案数,由于放两枚棋子的情况会
    被统计两次,最后还需减去摆两枚棋子的方案数,即第三项。
  • 代码:
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
const int N = (int) 1e7;
const int mod = (int) 1e9 + 7;
using namespace std;
long long fac[N+10], inv[N+10], a[N+10];
int n;
long long ex(int x, int y, int w) {
    if ((w = (w + y) % y) % x == 0)
        return w / x;
    return ((ex(y % x, x, -w) * y) + w) / x;
}
long long c(int n, int m) {
    return (long long) fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int main() {
    cin >> n;
    fac[0] = 1;
    for (int i = 1; i <= n; ++i)
        fac[i] = fac[i-1] * (long long) i % mod;
    inv[n] = ex(fac[n], mod, 1);
    for (int i = n; i >= 1; --i)
        inv[i-1] = (long long) inv[i] * i % mod;
    a[0] = 1;
    for (int i = 1; i <= n; ++i)
        a[i] = (2LL * i * a[i - 1] % mod - ((long long) (i-1) * (i-1)) % mod * a[i - 2] % mod + mod) % mod;
    long long ans = 0;
    for (int i = 0; i <= n; ++i)
        ans = (ans + ((i & 1 ? mod - 1LL : 1LL) * c(n, i) % mod * c(n, i) % mod * fac[i] % mod * a[n-i] % mod * a[n-i] % mod)) % mod;
    cout << ans << endl;
}

全部评论
第一题。 为什么是N中种概率而不是N的阶层中概率? 这里没懂。 我想的是把每一种情况的时间算出来,然后除以N 的阶层。
点赞 回复 分享
发布于 2017-06-19 12:00
qc霸霸让我夸夸你啊
点赞 回复 分享
发布于 2017-06-19 12:44
厉害。。
点赞 回复 分享
发布于 2017-06-19 13:05
qc聚聚现在是终板嘛?
点赞 回复 分享
发布于 2017-06-19 13:32
第二题是否存在如下情况 比如两个子串不能combine成回文,但是分别包含这两个子串的两个字符串combine之后是否有可能构成回文呢? 根据题解,这种情况是否定的,请问如何证明?
点赞 回复 分享
发布于 2017-06-21 14:20
所以大佬做题跟切菜一样吗( ⊙ o ⊙ )
点赞 回复 分享
发布于 2017-06-21 21:52
倒水 对于第三种情况,T 介于中间。 为什么会无解呀? 我自己没想明白,求各位指点迷津!!
点赞 回复 分享
发布于 2017-06-23 09:58
6
点赞 回复 分享
发布于 2017-06-24 09:04

相关推荐

09-20 09:17
已编辑
中国矿业大学 机械设计师
点赞 评论 收藏
分享
点赞 25 评论
分享
牛客网
牛客企业服务