Codeforces Round #688 (Div. 2)

A. Cancel the Trains

题意:竖着的有好多车站,可能有火车。横着的也是一样。现在告诉你横着的哪些有火车,竖着的哪些点有火车出发。火车出发时间相同,速度相同。底下的火车只能往上走。左边的只能往右。可能有的火车会发生碰撞。问最小取消掉多少火车,就不会发生碰撞了。

Codeforces Round #688 (Div. 2)_第1张图片
Codeforces Round #688 (Div. 2)_第2张图片

思路:题目看着一大坨,其实就是判断两个数组有多少个相同元素。暴力。

AC代码:

#include 
#define int long long
using namespace std;
const int N = 1e6+7;
const int mod = 1e9+7;
int t,n,m,k;
int a[N],b[N];
int res[N];
 
signed main(){
    cin>>t;
    while(t--){
        cin>>n>>m;
        for(int i = 0 ; i < n ; i ++) cin>>a[i];
        for(int i = 0 ; i < m ; i ++) cin>>b[i];
        int cnt = 0;
        for(int i = 0 ; i < n ; i ++){
            for(int j = 0 ; j < m ; j ++){
                if(a[i] == b[j]) cnt ++;
            }
        }
        cout<<cnt<<endl;
    }
    return 0;
}

B. Suffix Operations

题意:一个数组。可以对数组的后缀同时进行+1或者-1操作。就是每次后 i 个元素同步操作。而且可以删掉一个数。问 最少需要操作多少次,能够变成全部相同的值。

思路:对后缀进行操作。那肯定得先让a[n]变成a[n-1],然后 a[n-1] 变成 a[n-2],操作次数就是他们的差值绝对值。 然后可以删掉一个数。其实就相当于是可以跳过一个数。然后枚举每个数删掉的情况下的答案。就可以得到最优解了。

AC代码:

#include 
#define int long long
using namespace std;
const int N = 1e6+7;
const int mod = 1e9+7;
int t,n,m,k;
int a[N],b[N];
int res[N];
 
signed main(){
    cin>>t;
    while(t--){
        cin>>n;
        for(int i = 0 ; i < n ; i ++) cin>>a[i];
        if(n <= 2){
            cout<<0<<endl;
            continue;
        }
        int maxx = 0;
        int sum = 0;
        for(int i = n-2 ; i >= 0 ; i --){
            sum += abs(a[i]-a[i+1]);
        //    maxx = max(maxx,abs(a[i]-a[i+1]));
        }
        int minn = sum-max(abs(a[n-2]-a[n-1]),abs(a[0]-a[1]));
        for(int i = n-3 ; i >= 0 ; i --){
            int tmp1 = abs(a[i]-a[i+1])+abs(a[i+1]-a[i+2]);
            int tmp2 = abs(a[i]-a[i+2]);
            minn = min(minn,sum-tmp1+tmp2);
        }
        //cout<
        cout<<minn<<endl;
    }
    return 0;
}

C. Triangles

题意:一个n*n的数字矩阵。相同的数可以连成边。并且,可以改其中的一个数字。求一个三角形,三角形的一边平行于 x轴或者y轴,就是水平或者数值。求这个最大的三角形的面积的。

思路:枚举所有点作为三角形的一个顶点。然后要造一个三角形。可以改一个数。那么就造两次。一次造横着的边。也就是在水平方向上,这个点能延申的最大距离。然后在找出,在竖直方向上离他距离最远的数字相同的点。然后另一个就是造一条竖直方向上的边,然后找水平方向上最远的数字相同的点。然后去最大值。最后的答案就是所有的三角形的最大值。 需要预处理的就是,距离当前点的最远的点。其实只要找到这个数字,在两个方向上的最高点和最低点就行了 。然后就可以算哪个距离更大,就选哪个。

AC代码:

#include 
#define int long long
using namespace std;
const int N = 1e6+7;
const int mod = 1e9+7;
int t,n,m,k;
int a[N],b[N];
int res[N];
char s[2005][2005];
int l[10],r[10],u[10],d[10];
 
signed main(){
    cin>>t;
    while(t--){
        cin>>n;
        for(int i = 0 ; i < 10 ; i ++){
            l[i] = u[i] = 1e18;
            r[i] = d[i] = 0;
        }
        for(int i = 0 ; i < n ; i ++) cin>>s[i];
        for(int j = 0 ; j < n ; j ++){
            for(int k = 0 ; k < n ; k ++){
                int id = s[j][k] - '0';
                l[id] = min(l[id],k);
                r[id] = max(r[id],k);
                u[id] = min(u[id],j);
                d[id] = max(d[id],j);
            }
        }
        int res[10] = {0};
        for(int i = 0 ; i < n ; i ++){
            for(int j = 0 ; j < n ; j ++){
                int id = s[i][j]-'0';
                int h = max(i,n-i-1);
                int w = max(j,n-j-1);
                int w1=0,h1=0;
                w1 = max(abs(j-l[id]),abs(j-r[id]));
                h1 = max(abs(i-u[id]),abs(i-d[id]));
                //cout<
                int tmp1 = w1*h;
                int tmp2 = w*h1;
                res[id] = max(res[id],max(tmp1,tmp2));
            }
        }
        for(int i = 0 ; i < 10 ; i ++){
            cout<<res[i]<<" ";
        }
        cout<<endl;
    }
    return 0;
}

D. Checkpoints

题意:一种游戏。很多关,有的关卡是复活点。有的不是。打每一关都有1/2的概率获胜,获胜就可以继续往下走,反之,要回到离当前最近的复活点,然后重新开始打。给出最后的期望通关次数。求关卡的分布。

思路:找数学递推公式。推出 1000这样排的情况下,期望次数是是多少,结论是:对于任意情况的n时,f =2n+1−2。然后100100这样的就可以拆成100 + 100 的期望次数。然后就可以很好找出答案了。每个期望值都对应一种光卡的排列,把他们合并起来就好了。

数学永远的痛!递推过程可以参考博客:https://www.cnblogs.com/ghostlx/p/14090287.html

AC代码:

#include 
#define int long long
using namespace std;
const int N = 4e5 + 10;
const int mod = 1e9+7;
const int inf = 1e18;
int t,n,m,k;
int a[N],b[N],c[N];
int ans[N];
void test(){
   exit(0);
}
int pre[100];
void prework(){
    int now = 2;
    for(int i = 1; now <= inf; i ++){
        pre[i] = now-2;
        now <<= 1;
    }
}
 
signed main(){
    prework();
    // test();
 
    int t = 1; cin>>t;
    while(t--){
        cin>>n;
        if(n&1){ cout<<-1<<endl; continue; }
        int res = 0;
        for(int i = 59 ; i >= 2 && n; i--){
            while(pre[i] <= n){
                n -= pre[i];
                //cout<
                ans[res++] = 1;
                for(int j = 2 ; j < i ; j ++){
                    ans[res++] = 0;
                }
            }
        }
        //cout<
        cout<<res<<endl;
        for(int i = 0 ; i < res ; i ++){
            cout<<ans[i]<<" ";
        }
        cout<<endl;
    }
    return 0;
}

E. Dog Snacks

题意:一颗树。树上每个点都有一个饼干。现在有一只狗,从根节点出发,并且要吃掉所有的饼干并且回到根节点。狗会每次找最近的饼干吃。然是距离超过k的,就吃不到了。如果不能保持都有饼干在k距离以内,任务就失败了。现在就是给出这样一棵树。然后让你求最小的k,使得任务可以完成。

思路:树形DP。对于每一个点,贪心的考虑当前点所需要的k。

假设现在在 根节点。 那么要吃掉整棵树。并且跳回来。如果有多颗子树。那肯定是先吃小的那几颗先。然后,最后吃的是深度最大的。并且深度最大的要跳回根节点。如果第二大的链。那么吃完之后,就要经过根节点,跳到最大的那跳链上去。
如果不是在根节点。那么当前点如果是叶子。就需要跳回根节点。要去吃一颗子树,肯定是希望,吃完这颗子树之后,停留的位置,深度越小越好。所以dp[pos],表示pos的子树吃完能停留的最小深度。每颗子树吃完之后,都要跳到父亲节点的下一条链去。

AC代码:

#include 
#define int long long
using namespace std;
const int N = 4e5 + 10;
const int mod = 1e9+7;
const int inf = 1e18;
int t,n,m,k;
int a[N],b[N],c[N];
int ans[N];
vector<int> edge[N];
 
void test(){
   exit(0);
}
void prework(){
}
int res = 0;
int dp[N];
 
void dfs(int pos,int fa,int dep){
    vector<int> depp;
    for(auto i : edge[pos]){
        if(i!=fa){
            dfs(i,pos,dep+1);
            depp.push_back(dp[i]);
        }
    }
    sort(depp.begin(),depp.end());
    int sz = depp.size();
    if(sz == 0){     // leaf
        dp[pos] = dep;
    }else if(pos != 1){ // node
        dp[pos] = depp[0];
        res = max(res,depp[sz-1]-dep+1);
    }else if(pos == 1){  // root
        if(sz > 0) res = max(res,depp[sz-1]-dep);
        if(sz > 1) res = max(res,depp[sz-2]-dep+1);
    }
}
signed main(){
    prework();
    // test();
    int t = 1; cin>>t;
    while(t--){
        int n;cin>>n;
        for(int i = 1 ; i <= n ; i ++){
            edge[i].clear();
        }
        for(int i = 1 ; i < n ; i ++){
            int x,y;
            cin>>x>>y;
            edge[x].push_back(y);
            edge[y].push_back(x);
        }
        res = 0;
        dfs(1,-1,0);
        cout<<res<<endl;
    }
    return 0;
}

F. Even Harder

题意:给定一个数组,开始在0位置,每个位置都可以移动到 i+1≤ j ≤i+ai,也就是可以跳 到下 a[i] 格子以内的各自。现在要求 把多少个点改成0可以使得从1-n只有一条唯一路径,求最小的要改变的个数。

思路:状态好难想。f[i][j+a[j] ] 表示的是,从j点出发到s,且最远能到 j+a[j],的最小修改次数。要确保唯一性,就得保证,j - i 之内的,所有可以到达所有 i 的点都被删掉了。所以这个用cnt记录下来。这个很好维护。先枚举i,然后倒着枚举 j,如果j 能到i,那么显然 cnt 就+1 。所以转移就是:

if(j+a[j] >= i){
   dp[i][j+a[j]] = min(dp[i][j+a[j]],dp[j][i-1]+cnt);
    cnt++;
}

AC代码:

#include 
#define int long long
using namespace std;
const int N = 4e5 + 10;
const int mod = 1e9+7;
const int inf = 1e18;
int t,n,m,k;
int a[N],b[N],c[N];
int ans[N];
vector<int> edge[N];
int dp[3005][3005];
int cnt[3005][3005];
int minn[30005];
 
signed main(){
    int t = 1; cin>>t;
    while(t--){
        int n;cin>>n;
        for(int i = 1; i <= n ; i ++) cin>>a[i];
        for(int i = 2; i <= n ; i ++) dp[1][i] = 0;
        for(int i = 2; i <= n ; i ++){
            int cnt = 0;
            for(int j = i ; j <= n ; j ++) dp[i][j] = inf;
            for(int j = i-1 ; j >= 1 ; j --){  // start point
                if(j+a[j] >= i){
                    dp[i][j+a[j]] = min(dp[i][j+a[j]],dp[j][i-1]+cnt);
                    cnt++;
                }
            }
            for(int j = i+1 ; j <= n+1 ; j ++){
                dp[i][j] = min(dp[i][j],dp[i][j-1]);
            }
        }
        cout<<dp[n][n]<<endl;
    }
    return 0;
}

你可能感兴趣的:(Div2,2021寒假,算法练习,Codeforces)