2020牛客暑期多校训练营第二场

B Boundary

对于圆而言有三点定圆定理和相关公式。
2020牛客暑期多校训练营第二场_第1张图片
其中x0,y0坐标为圆心坐标。
我们利用三点定圆定理求出的圆心坐标来判断是否点在同一圆上。

除开原点,每次确定圆还需要两个点。若圆上除了原点还有N个数据点,那么每个数据点都能和其他(N-1)个点确定这个圆,那么我们通过两个点确定的圆心数和圆上的点数有这样的关系。
C(N,2) *(圆上的点的数量) = 圆心的重复出现的次数。

其实比赛是可以做出来的,因为思维固化习惯性用STL(map),忘记了sort,超时。

代码:

#include
using namespace std;
typedef long long ll;
const int maxn = 4e6 + 5;
const double eps = 1e-8;
typedef struct Point{
    double x,y;
    bool operator <(const Point & pp){
        if(fabs(x-pp.x)>eps)return x - pp.x > eps;
        if(fabs(y-pp.y)>eps)return y - pp.y > eps;
    }
}P;
int num[maxn];
double a,b,c,d,f,e;
int N,cnt= 0;
P p[5000];
double pro[maxn];
P rp[maxn];
void init(){
    for(int i = 1;i <= 2000;i++){
        num[i*(i-1)/2] = i;
    }
}
int main(){
    init();
    cin >> N;
    for(int i = 1;i <= N;i++){
        cin >> p[i].x >> p[i].y;
    }
    if(N<=1){
        cout << N <<endl;   
    }
    else {
    for(int i = 1;i <= N-1;i++){
        a = -p[i].x;b = -p[i].y;
        for(int j = i+1;j <= N;j++){
            if(p[i].x*p[j].y==p[j].x*p[i].y)continue;
            c = -p[j].x;d = -p[j].y;
            e = -(p[i].x*p[i].x+p[i].y*p[i].y)/2;
            f = -(p[j].x*p[j].x+p[j].y*p[j].y)/2;
            ++cnt;
            rp[cnt].x = -(d*e - b*f)/(b*c - a*d);
            rp[cnt].y = -(a*f - c*e)/(b*c - a*d);
          
        }
    }
    sort(rp + 1, rp+1+cnt);
    P sp;
    int tot = 0;
    int ans = 0;
    sp.x = rp[1].x;sp.y = rp[1].y;
    for(int i = 1;i <= cnt;i++){
       if(fabs(sp.x-rp[i].x)<=eps&&fabs(sp.y-rp[i].y)<=eps)tot++;
       else {
           ans = max(ans,tot);
           tot = 1;
           sp.x = rp[i].x;sp.y = rp[i].y;
       }
    }
      ans = max(ans,tot);
      cout << num[ans]<<endl;
    }
    return 0;
}

C Cover the Tree

要走完每一条边,要关心的主要是叶子结点连接的边。
当然单纯不是叶子结点随便的连接,要根节点的不同子树上的叶子结点连接才能保证所有边都能被至少包含一次。
对于题中的无根树,随意的非叶子点都可以当做一个根来操作。dfs找叶子,叶子就能根据子树分块(在访问顺序上表现为一个子树上的在一起),按照这样的规律就两个不同子树的叶子连接就完事了。

#include
using namespace std;
const int maxn = 2e5+5;
int N,x,y,root;
int flag[maxn],num[maxn],cnt = 0;
vector<int> v[maxn],Le;
void dfs(int x){
     if(v[x].size()==1){
         Le.push_back(x);
         return;
     }
      for(int i = 0;i < v[x].size();i++){
         if(flag[v[x][i]]!=1){
          flag[x] = 1;
          dfs(v[x][i]);
         }
      }
}
int main(){
    cin >> N;
    for(int i = 1;i <= N-1;i++){
        cin >> x >> y;
       v[x].push_back(y);
       v[y].push_back(x);
    }
    for(int i = 1;i <= N;i ++){
        if(v[i].size()!=1){
            root = i;
            dfs(i);
            break;
        }
    }
    cout << (Le.size()+1)/2 <<endl;
    for(int i = 0;i < (Le.size()+1)/2 ;i++)
        cout << Le[i] <<" "<<Le[(i+Le.size()/2)%Le.size()]<<endl;
}

J Just Shuffle

了解了一下置换群,置换群其实操作还挺多,只是做这题比较简单。
找打置换群的操作下K的逆元。
A k = B , B x = A 1 ⇒ k x = 1 ( m o d R ) A^{k} = B,B^{x} = A^{1} \Rightarrow kx=1(modR) Ak=B,Bx=A1kx=1(modR)其中R是循环节
算出x,把B置换x次便可以得到答案。其实接受置换群的一些设定,就很简单了。但是我觉得还是得细品一些置换群,因为其他题目可以转入置换群的切入角度会简单很多。

明天自己写

F Fake Maxpooling

本题就是用按照题目要求用单调对列把k * k的矩阵压缩,第一次压缩成了1 * k,第二次就成了1 * 1的矩阵,即是答案。

最开始我觉得lcm可能是突破点,是一个规律,很可惜没有找到。
其实这题很类似去年牛客多校的部分题,不过那些题是单调栈,这就是单调对列。感觉暴力可以过,就STL暴力,卡了小部分样例。

#include
using namespace std;
const int maxn = 5e3+5;
int N,M,K;
typedef long long ll;

int Max[maxn][maxn],lcm[maxn][maxn];
int que[maxn];

int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}

void Init(){
    for(int i = 1;i <= N;i++){
        for(int j = 1;j <= M;j++){
             if(lcm[i][j]==0){
                 lcm[i][j] = i/(gcd(i,j))*j;
                 lcm[j][i] = lcm[i][j];
             }
        }
    }
}

void test(){
    for(int i = 1;i <= N;i++){
        for(int j = K;j <= M;j++){
            cout << Max[i][j]<<" ";
        }
        cout <<endl;
    }
}
int main(){
    cin >> N >> M >>K;
    Init();
    int fro,re,i,j;
    for(i = 1;i <= N;i++){
        fro = 1;re = 0;
        for(j = 1;j <= K;j++){
            que[++re] = lcm[i][j];
            while((re>fro&&que[re]>=que[fro])||j-fro>=K)fro++;
        }
        Max[i][j-1] = que[fro];
        for(;j<=M;j++){
            que[++re] = lcm[i][j];
            while((re>fro&&que[re]>=que[fro])||j-fro>=K)fro++;
            Max[i][j] = que[fro];
        }
        
    }
    ll ans = 0;
    for(j = K;j <= M;j++){
         fro = 1;re = 0;
        for(i = 1;i <= K;i++){
            que[++re] = Max[i][j];
            while((re>fro&&que[re]>=que[fro])||i-fro>=K)fro++;
        }
        ans += 1ll*que[fro];
        for(;i<=N;i++){
            que[++re] = Max[i][j];
            while((re>fro&&que[re]>=que[fro])||i-fro>=K)fro++;
            ans += 1ll*que[fro];
        }
    }
    cout << ans <<endl;
    return 0;
}

G Greater and Greater

开始享用KMP的next数组去实现来简化匹配。
然后看到大佬代码,不禁露出了 楚云飞.JPG 的表情。
大佬就是牛啊,简单的bitset被榨到一滴不剩。

为啥能用bitset呢?因为大小关系01可表示。
为啥要用bitset呢?内存小操作便。
来说一个十分巧妙的操作,甚至不需要dp,bitset它就是主角。
我们先将A,B排序,这可以减少时间复杂度。
一个tmp记录每个比 B i B_{i} Bi的大小情况。由于 A i A_{i} Ai排序好了,那当前的比 B j B_{j} Bj大,那么后面的也比 B j B_{j} Bj大。只需遍历一遍即可。
看样例

6 3
1 4 2 8 5 7
2 3 3

对A,B排序得到 A ′ , B ′ A^{'},B^{'} A,B
A ′ [ . . . . ]     = 1        2        4        5        7        8 A^{'}[....]\ \,=1\,\,\,\,\,\, 2\,\,\,\,\,\,4\,\,\,\,\,\,5\,\,\,\,\,\,7\,\,\,\,\,\,8 A[....] =124578
a i n d e x = 1        3        2        5        6        4 aindex = 1\,\,\,\,\,\, 3\,\,\,\,\,\, 2\,\,\,\,\,\, 5\,\,\,\,\,\, 6\,\,\,\,\,\, 4 aindex=132564

B ′ [ . . . . ]     = 2        3        3 B^{'}[....]\ \,=2\,\,\,\,\,\, 3\,\,\,\,\,\,3 B[....] =233
b i n d e x   = 1        2        3 bindex\ = 1\,\,\,\,\,\, 2\,\,\,\,\,\, 3 bindex =123

B 1 ′ = 2 B^{'}_{1}=2 B1=2时,比 B j 1 ′ B^{'}_{j1} Bj1小的有
A 1 ′ A^{'}_{1} A1,且 b i n d e x 1 = 1 bindex_{1}=1 bindex1=1,故 a i n d e x 1 aindex_{1} aindex1的为开头的子区间无法和B匹配,因为 A 1 = A 1 ′ A_{1}=A^{'}_{1} A1=A1< B 1 ’ = B 1 B^{’}_{1}=B_{1} B1=B1,故A以第1个元素对B第1个元素的子区间不满足要求,ans[1] = 0;

B 2 ′ = 3 B^{'}_{2}=3 B2=3时,比 B 2 ′ B^{'}_{2} B2小的有
A 1 ′ , A 2 ′ A^{'}_{1},A^{'}_{2} A1,A2,且 b i n d e x 2 = 2 bindex_{2}=2 bindex2=2,( A 1 ′ A^{'}_{1} A1无法和位置为2的 B 2 ′ B^{'}_{2} B2区间长度无法对应不讨论)故 a i n d e x 2 aindex_{2} aindex2的为第二个元素的子区间无法和B匹配,因为 A 3 = A 2 ′ A_{3}=A^{'}_{2} A3=A2< B 2 ′ = B 2 B^{'}_{2}=B_{2} B2=B2,故A以第3个元素对B第2个元素的子区间不满足要求,即A以第2个元素对B第1个元素的子区间不满足要求,ans[2] = 0;

B 3 ′ = 3 B^{'}_{3}=3 B3=3时,比 B 3 ′ B^{'}_{3} B3小的有
A 1 ′ , A 2 ′ A^{'}_{1},A^{'}_{2} A1,A2,只有 A 2 ′ A^{'}_{2} A2区间长度可以对应,可以推出。
A以第3个元素对B第3个元素的子区间不满足要求,即故A以第1个元素对B第1个元素的子区间不满足要求,ans[1] = 0;

就这样遍历一遍,所有ans[1~N-M+1]都出来了,答案也出来了
a n s [ . . . . ]     = 0        0        1        1        1        1 ans[....]\ \,=0\,\,\,\,\,\, 0\,\,\,\,\,\,1\,\,\,\,\,\,1\,\,\,\,\,\,1\,\,\,\,\,\,1 ans[....] =001111
但是只有1 ~ (N-M+1)可以作为开头,那么只需管1~(N-M+1)的结果即可。也就是0 0 1 1
0      1   4   2 0 \ \ \ \ 1 \ 4 \ 2 0    1 4 2
   0       4   2   8 \ \ 0\ \ \ \ \ 4\ 2 \ 8   0     4 2 8
      1       2   8   5 \ \ \ \ \ 1\ \ \ \ \ 2\ 8 \ 5      1     2 8 5
         1       8   5   7 \ \ \ \ \ \ \ \ 1\ \ \ \ \ 8 \ 5\ 7         1     8 5 7
最终结果为2
代码在此,看了我上文写的,应该很好懂

#include
using namespace std;
const int maxn = 2e5;
int N,M;
struct seq{
    int num,pos;
    bool operator<(const struct seq & S){
        return num < S.num;
    }
}A[maxn],B[maxn];
bitset<maxn> tmp,ans;
int main(){
    cin >>N >>M;
    for(int i = 1;i <= N;i++){
        cin >> A[i].num;
        A[i].pos = i;
    }
    for(int i = 1;i <= M;i++){
        cin >> B[i].num;
        B[i].pos = i;
    }
    sort(A+1,A+1+N);
    sort(B+1,B+1+M);
    tmp.set();
    ans.set();
    int ppos = 1;
    for(int i = 1;i <= M;i++){
        while(ppos<=N&&A[ppos].num<B[i].num){
            tmp.reset(A[ppos].pos);
            ppos++;
        }
        ans = ans & (tmp >> (B[i].pos-1));
       
    }
    int cnt = 0;
    for(int i = 1;i <= N-M+1;i++){
        if(ans[i]&1)
            cnt++;
    }
    cout <<cnt<<endl;
    return 0;
}

你可能感兴趣的:(2020牛客暑期多校训练营第二场)