Educational Codeforces Round 136 (Rated for Div. 2)A~D(模拟、组合数\博弈、树的深度)

A. Immobile Knight

思路:其实不知道是怎么做的,但是找规律做的,比如(5,5)以上或者(1,1)都能走到,所以输出自己,另外的情况就会到不了,所以输出(n-1, m-1)

代码:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int t;cin>>t;
    while(t--){
    int n,m;cin>>n>>m;
    if(n>=5&&m>=5||n==1||m==1)cout<<n<<" "<<m<<endl;
    else cout<<n-1<<" "<<m-1<<endl;
    }
    return 0;
}

B. Array Recovery

很模拟的一道题,注意a[i]=0的情况

代码:

#include<bits/stdc++.h>
using namespace std;
int sum[103]={0};
int a[103];
int main()
{
    int t;cin>>t;
    while(t--){
        memset(sum,0,sizeof(sum)); memset(a,0,sizeof(a));
        int n;cin>>n;bool fla=0;
        for(int i=1;i<=n;i++)cin>>a[i];
        for(int i=1;i<=n;i++){
            sum[i]=sum[i-1]+a[i];
            if(i!=1&&sum[i-1]>=a[i]&&a[i]!=0){fla=1;cout<<-1;break;}
        }
        if(!fla)for(int i=1;i<=n;i++)cout<<sum[i]<<" ";cout<<endl;
    }
    return 0;
}

C. Card Game

题意:A和B手中有1~n的卡,每人拿n/2张卡。两人轮流出卡,如果有人出的卡对方出不了比他大的卡,那么对方输了

思路:

1. A有最大的卡,A胜利,此时A的卡的种类数为  \binom{n}{\frac{n}{2}-1}

2.A没有最大的卡,A有次大的卡。这时A出了次大的卡,B出了最大的卡,安全度过。

        2.1 A有次次大的卡,如果没有就输了

                2.1.1 A有次次次大卡,这时无论B出了什么,A都能赢,此时A的卡的种类数为  \binom{n}{\frac{n}{2}-3}(减3是因为有三张卡已经固定了,分别是次大卡和次次大卡)

                2.1.2 A没有次次次打卡,B有次次次大卡,于是又是平安度过,轮回到1,此时n=n-4

代码:

#include<bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
const int mod=998244353;
long long binpow(long long a, long long b, long long m) {
    a %= m;
    long long res = 1;
    while (b > 0) {
    if (b & 1) res = (__int128)res * a % m;
    a = (__int128)a * a % m;
    b >>= 1;
    }
return res;
}
ll C(ll n,ll m){
    ll shang=1,xia=1;
    for(int i=1;i<=m;i++){
        shang=shang*(n-m+i)%mod;
        shang=shang*binpow(i,mod-2,mod)%mod;
    }
    return  shang;
}
signed main()
{
    int T;cin>>T;
    while(T--){
        int n;cin>>n;int ans=0;
        int i=0;
        while(n-4*i>0){
            if(n-1-4*i>0)ans=(ans+C(n-1-4*i,(n-4*i)/2-1)%mod)%mod;
            if(n-4*(i+1)>0)ans=(ans+C(n-4*(i+1),(n-4*i)/2-3)%mod)%mod;
            i++;
        }
        cout<<ans<<" "<<(C(n,n/2)-ans-1+mod)%mod<<" "<<1<<endl;
    }
    return 0;
}

D. Reset K Edges

给定一棵根节点为 1 的树。可以执行下面的操作不超过 k 次:

        选择一条边 ( v , u ) 删掉,v 是 u 的父节点;
        增加一条边 ( 1 , u ) ,即让 u 与根节点相连。
根节点深度为 0,求树的高度最小为多少?

思路:可以用二分做,二分树的深度。

那么关于怎么check。

        一开始想遍历n,在超过mid的地方就把它接在1的下面,但是假如3是2的结点,那么就错了,所以应该用层序遍历;

        但是问题又来了,层序遍历从高往下的话,假如我改完了一次,那新改动的部分还要再重复会被改动;

        所以答案中 树是从下到上的,从最底层向上,假如有一个点超出了二分深度,那么就把它移到上面来,这样就不会重复处理了。

在check的代码层面上:

预处理:先把每个点(也可以除了1)的cnt都记为1,因为除了1的每个点都会有一个父亲,(如果父亲是1的话cnt就是当前子树的深度),所以深度都设为1,有了儿子后,自身的深度还会加1。

判断:

1 若x是1,那么不适用这个cnt的设计,所以参加判断的一定不是1。

2 若cnt大于等于二分的mid且父亲不是1,那么把它移走,cnt设为0,代表这一部分已经完全不用理了,即使是x的父亲求深度的时候也不会有任何影响。

3 若cnt等于二分的mid且父亲是1,没影响。

4 若cnt大于二分的mid且父亲是1,不存在这个情况,因为在等于的时候情况2就已经判断过了。

代码:

#include<bits/stdc++.h>
using namespace std;
vector<int> g[200005];
int cnt[200005],f[200005];
struct node{
    int id;
    int h;
}a[200005];
int n,k;
bool cmp(node &a,node &b){return a.h>b.h;}
void init()
{
    a[1].id=1;a[1].h=1;
    for(int i=1;i<=n;i++){
        for(auto x:g[i]){
            a[x].h=a[i].h+1;a[x].id=x;
        }
    }
}

bool check(int mid)
{
    for(int i=1;i<=n;i++)cnt[i]=1;
    int cou=0;
    for(int i=1;i<=n;i++){
        int x=a[i].id;
        for(auto q:g[x]){cnt[x]=max(cnt[x],cnt[q]+1);}
        if(cnt[x]>=mid&&x!=1&&f[x]!=1){
            cou++;cnt[x]=0;//这样父节点遍历的时候就还是1了
        }
        if(cou>k)return 0;
    }
    return 1;
}

int main()
{
    int T;cin>>T;
    while(T--){
        cin>>n>>k;
        for(int i=1;i<=n;i++) g[i].clear();
        for(int i=2;i<=n;i++){
            int fa;cin>>fa;g[fa].push_back(i);f[i]=fa;
        }
        init();
        sort(a+1,a+1+n,cmp);
        int l=1,r=n;int ans=-1;
        while(l<=r){
            int mid=(l+r)>>1;
            if(check(mid)) ans=mid,r=mid-1;
            else l=mid+1;
        }
        cout<<ans<<'\n';
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zy98zy998/article/details/127322026