Educational Codeforces Round 64部分题解

A

题目大意:给定三角形(高等于低的等腰),正方形,圆,在满足其高,边长,半径最大(保证在上一个图形的内部)的前提下.

判断交点个数是否有限,如果有限,输出.

很明显当正方形套三角形或者三角形套正方形是交点个数是无限的(因为有一条边相交)

其他图形的嵌套交点个数比较好判断,不多赘述

但是注意坑点:

当按照矩形,园,三角这样的顺序是,三角与圆的一个交点是与圆和正方形的交点重合的,判一下就好了

#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cctype>
using namespace std;
int n;
inline int read(){
    int v = 0,c = 1;char ch = getchar();    
    while(!isdigit(ch)){
        if(ch == '-') c = -1;
        ch = getchar(); 
    }
    while(isdigit(ch)){
        v = v * 10 - 48 + ch;
        ch = getchar(); 
    }
    return v * c;
}
int a[312312];
bool flag = 1;
int ans;
int main(){
    n = read();
    for(int i = 1;i <= n;++i) a[i] = read();
    for(int i = 2;i <= n;++i){
        if(a[i] == 2 && a[i - 1] == 3) flag = 0;    
        if(a[i] == 3 && a[i - 1] == 2) flag = 0;
        if(a[i] == 1){
            if(a[i - 1] == 2) ans += 3;
            if(a[i - 1] == 3) ans += 4; 
        }
        if(a[i] == 2){
            if(a[i - 1] == 1) ans += 3; 
            if(i > 2 && a[i - 2] == 3) ans--;
        }
        if(a[i] == 3 && a[i - 1] == 1){
            ans += 4;
            
        }
    }
    if(flag) printf("Finite\n%d\n",ans);
    else printf("Infinite\n");
    return 0;   
}

B

题目大意:给定一个字符串,将其重新排序,使得任意两个相邻的字母在字母表上不相邻(\(a\)\(z\)不相邻)

这道题当时卡了幸好unrated

很明显,我们将奇数字符和偶数字符分开考虑,这样就保证了奇数之间和偶数之间不会出现上述情况,唯一需要考虑的就是奇数和偶数的交接部分.我们想,如果奇数的最后一个和偶数的第一个相接或者偶数的第一个和奇数的最后一个 相接都不能满足,那么就无解(因为我们这样已经尽可能地去保障了他们不会出现上述情况)

代码先咕一咕吧

C

题目大意:给定\(n\)个数,求出最多的点对\((i,j)\)使得\(|{a_i-a_j}|>=k\)

求一个\(nlogn\)的做法

很明显的一个错误贪心:

排序之后,对于每个\(a_i\),都找到他后面第一个\(a_j\)使得\(a_j-a_i>=k\),这些操作直接在\(multiset\)(我当时手写FHQ也是没谁了)上搞一搞就好了

但是,很明显是错的.

看下面一组数据

10 2

1 2 3 4 5 6 7 8 9 10答案为\(5\)

但是用上述方法答案是\(4\)

之后我们发现,我们排完序之后,答案最多为\(\frac{n}{2}\)(下取整),所以至少将前\(\frac{n}{2}\)个与后\(\frac{n}{2}\)个去匹配(很明显随着第一个指针右移,第二个也会右移),这个直接维护双指针,维护当前可用的最小的\(p\)就好了

#include<cstdio>
#include<iostream>
#include<ctime>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int N = 2e5 + 3;
int a[N];
int n,k,ans;
int main(){
    scanf("%d%d",&n,&k);    int p = n / 2 + 1;
    for(int i = 1;i <= n;++i) scanf("%d",&a[i]);
    sort(a + 1,a + n + 1);
    for(int i = 1;i <= n / 2;++i){
        for(;p <= n;++p)
            if(a[p] - a[i] >= k) break;
        
        if(p > n) break;
        ans++;p++;
    }
    printf("%d\n",ans);
    return 0;   
}

D

题目大意:给定一个边权为\(0/1\)的树,求出满足从\(x\)点到\(y\)点不会出现经过边权为\(1\)的边之后在经过边权为\(0\)的边的点对\((x,y)\)的个数

要求一个\(nlogn\)或者更优的算法

这道题听说可以用\(dsu\)去做,以后再学(flag)

我们考虑\(dp\)

\(dp_{i,0/1}\)表示以\(i\)点为根时,只经过边权为\(0/1\)的边就到达\(i\)的点的个数

想一想,如果我们能够求出这个东西

那么

\(ans = \sum_{i = 1}^n dp_{i,0} * dp_{i,1} + dp_{i,0}+dp_{i,1}\)

所以难点在于计算出\(dp\)数组

先考虑在以\(i\)为根的子树中

很明显则有

\(dp_{i,w} = \sum_{j\in son_i}(dp_{j,w} + 1)\)

这样我们就可以求出在\(i\)的子树中的值了,但是我们想要的是其为根时候的\(dp\)

那么我们进行第二遍\(dfs\),想一下

去推出由父亲到儿子的贡献

如果所有点到其父亲的路径是\(dp_{i,w}\)

那么到他自己也是\(dp_{i,w}\)(因为他们两个只有一条边的距离,且这条边的边权为\(w\))

在递归处理就好了

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#define mk make_pair
#define pii pair<int,int>
#define LL long long
using namespace std;
const int N = 2e5 + 3;
vector <pii> G[N];
LL f[N][2];
int n;
inline void dfs1(int x,int fa){
    for(int i = 0;i < G[x].size();++i){
        int y = G[x][i].first,z = G[x][i].second;
        if(y == fa) continue;
        dfs1(y,x);
        f[x][z] += f[y][z] + 1;
    }
}
inline void dfs2(int x,int fa){
    for(int i = 0;i < G[x].size();++i){
        int y = G[x][i].first,z = G[x][i].second;
        if(y == fa) continue;
        f[y][z] = f[x][z];
        dfs2(y,x);
    }
}
inline int read(){
    int v = 0,c = 1;char ch = getchar();    
    while(!isdigit(ch)){
        if(ch == '-') c = -1;
        ch = getchar(); 
    }
    while(isdigit(ch)){
        v = v * 10 - 48 + ch;
        ch = getchar(); 
    }
    return v * c;
}
int main(){
    n = read();
    for(int i = 1;i < n;++i){
        int x = read(),y = read(),z = read();
        G[x].push_back(mk(y,z));
        G[y].push_back(mk(x,z));    
    }
    dfs1(1,0);
    dfs2(1,0);
    LL ans = 0;
    for(int i = 1;i <= n;++i)
    ans += f[i][0] * f[i][1] + f[i][0] + f[i][1];
    cout << ans << endl; 
    return 0;   
}

E

题目大意,给定一个\(n\)的排列,求出满足\(a_l+a_r=\max_{i = l}^ra_i\)的区间\([l,r]\)的个数

我们直接寻找最大值去分治,数据随机的话,是可以\(nlogn\)的,但是就跟着\(noip2019 D1T1\)一样,如果每次最大值都靠向两边,复杂度是会退化为\(n^2\)

之后我们想如果对于每个\(a_i\),我们都求出以其为最大值的区间的最大左右端点\(l_i,r_i\),之后我们设其到\(l_i\)的距离为\(L\),

\(r_i\)的距离为\(R\),我们可以找到一个\(T(n) = T(L) + T(R) + n\)的做法,这个很明显没有进行优化.

但是,我们想一下,以\(a_i\)为最大值的区间端点必须在$l_i,r_i $之间,那么我们每次找到 $ L,R$中较小的一个去进行枚举.

之后查询\(a_i - a_j\)的位置是否在相反的一边,这样的话时间复杂度变成了\(nlog(n)\)

#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<deque>
#define LL long long
using namespace std;
const int N = 2e5 + 45;
int pos[N];
int a[N],l[N],r[N];
int n;
deque <int> q;
inline int read(){
    int v = 0,c = 1;char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') c = -1;
        ch = getchar(); 
    }
    while(isdigit(ch)){
        v = v * 10 + ch - 48;
        ch = getchar(); 
    }
    return v * c;   
}
int main(){
    n = read();
    for(int i = 1;i <= n;++i) a[i] = read(),pos[a[i]] = i;
    for(int i = 1;i <= n;++i){
        while(!q.empty() && a[q.back()] < a[i]) q.pop_back();
        l[i] = q.empty() ? 1 : q.back() + 1;
        q.push_back(i);
    }
    q.clear();
    for(int i = n;i >= 1;--i){
        while(!q.empty() && a[q.back()] < a[i]) q.pop_back();
        r[i] = q.empty() ? n : q.back() - 1;    
        q.push_back(i);
    }
    LL ans = 0;
//  for(int i = 1;i <= n;++i) printf("%d %d\n",l[i],r[i]);
    for(int i = 1;i <= n;++i){
    //  printf("a[i]:%d %d %d\n",a[i],l[i],r[i]);
        if(i - l[i] < r[i] - i){
            for(int j = l[i];j < i;++j)
            if(pos[a[i] - a[j]] <= r[i] && pos[a[i] - a[j]] > i) ans++;
        //  printf("%d %d\n",j,pos[a[i] - a[l[i]]]);
        }
        else{
            for(int j = r[i];j > i;--j){
                if(pos[a[i] - a[j]] >= l[i] && pos[a[i] - a[j]] < i) ans++;
            }
        }
    }
    cout << ans << endl;
    return 0;   
}