atcoder补题:262

发布于:2023-01-08 ⋅ 阅读:(396) ⋅ 点赞:(0)

https://atcoder.jp/contests/abc262

 

C - Min Max Pair :思维

 

样例:

4
1 3 2 4
结果:2
  1. 题意:给定n个数字,且数字范围是1-n,然后如果满足以下等式则ans++,求ans个数;如图

  2. 当a[i]=i时,则a[j]=j;统计所有a[i]=i的点,从中任意选两个,即n*(n-1)/2;

  3. 当a[i]!=i,则a[i]=j,a[j]=i;这样的(i,j)是一组答案;

  4. code

    #include <bits/stdc++.h>
    using namespace std;
    ​
    int main() {
        int N;
        cin >> N;
    ​
        int a[N];
        long long n = 0;
        long long ans;
        for (int i=1; i<=N; ++i) {
            cin >> a[i];
            if (i == a[i]) ++n;
        }
        ans = ((n-1)*(n-1)+(n-1))/2;
    ​
        int j;
        for (int i=1; i<=N; ++i) {
            j = a[i];
            if ((a[j] == i) && (i < j)) ++ans;
        }
        cout << ans;
        return 0;
    }

    D - I Hate Non-integer Number :dp

 

样例:

3
2 6 2
输出:6
2,6,2,均选一个;3
4,2,4,均选2个; 3
10/3非整数;不选
  1. 题意:给定一个序列,对于该序列的所有子集,判断所有子集的平均值为整数的子集的有多少个;答案对998244353取模;

  2. 用j,k,l,表示;dp(j,k,l)表示前j个数中选k个数,其和%i为 l 的方案数;如果其平均数是整数,则对i取模后结果必为0;即dp(n,i,0)为所有n个数中任取i个数,其和的平均值为整数的方案数;

  3. 状态转移方程:不选a[i] : dp(j+1,k,l)+=dp(j,k,l); 选:dp(j+1 , k+1 , ( l+a[i] )%i ) + = dp( j,k,l ); u即l

  4. #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int N=110,mod = 998244353;
    ll dp[N][N][N]; 
    ll a[N];
    int main()
    {
        int n;
        cin>>n;
        for(int i=0;i<n;i++)
        cin>>a[i];
        ll res=0;
        for(int i=1;i<=n;i++)//最多i分 
        {
            memset(dp,0,sizeof dp);
            dp[0][0][0]=1;//初始值
            for(int j=0;j<n;j++)//从j个里面选 
            {
                for(int k=0;k<=i;k++)//j个里选k个 的情况 
                {
                    for(int u=0;u<i;u++)//余数 
                    {
                        dp[j+1][k][u]+=dp[j][k][u]%mod;//不选第j个 
                        if(k!=i)
                        {
                            dp[j+1][k+1][(u+a[j])%i]+=dp[j][k][u];
                        }
                    }
                }
                
            }
            res+=dp[n][i][0]%mod;
        }
        cout<<res%mod<<endl;
    }

    E - Red and Blue Graph:逆元求组合数

  5. 题意:给定无向图,每个点都可染成红色或蓝色,求满足:有k个点被染成红色;且满足两端点颜色不一样的边的个数为偶数的条件下,可能的方案数;

  6. 设两端点均是红色点的边的个数为a,一红一蓝的边数为b;则所有红色点的度之和为:s=2*b+c;故所有的红色点的度之和为偶数;即要找偶数个度数为奇数的点;假设选i个度数为奇数的红色的点,i一定要是偶数,则剩下k-i个点都是度数为偶数的点,才满足所有红色的点的度数和为偶数;

  7. #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int N=2e5+10,mod=998244353;
    int d[N];
    int n,m,k;
    int odd;
    ll ans=0;
    int fact[N],infact[N];
    int qmi(int a,int k,int p)
    {
        int res=1;
        while(k)
        {
            if(k&1)
            {
                res=(ll)res*a%p;
            }
            a=(ll)a*a%p;
                k>>=1;
        }
        return res;
    }//逆元取模 
    void init()
    {
        fact[0]=infact[0]=1;
        for(int i=1;i<N;i++)
        {
            fact[i]=(ll)fact[i-1]*i%mod;
            infact[i]=(ll)infact[i-1]*qmi(i,mod-2,mod)%mod;
        }
    }//初始化,前缀和 
    ll c(int a,int b)
    {
        return (ll)fact[a]*infact[b]%mod*infact[a-b]%mod;
    }//求组合数 
    void solve()
    {
        init();
        cin>>n>>m>>k;
        while(m--)
        {
            int x,y;
            cin>>x>>y;
            d[x]++,d[y]++;
        }//存度数d[];
        for(int i=1;i<=n;i++)
        {
            if(d[i]&1)
            odd++;
        }
        for(int i=0;i<=k;i+=2)//i必须为偶数; 
        {
            if(i>odd||k-i>n-odd)continue;
            ans=(ans+c(odd,i)*c(n-odd,k-i)%mod)%mod;//
        }
        cout<<ans%mod<<endl;
    }
    int main()
    {
        solve();
    }

    4.还是要好好学组合数‘逆元;

    F - Erase and Rotate

  8. 题意:给定一个序列p,长度为n,进行如下操作:选择任一个p[i]删除;或把最末尾的p[i]移动至最前方;问:最后得到的字典序最小的序列是.....

  9. 分析:每次操作使字典序更小;一定会操作k次,考虑两种方案:先找到最小的数字所在的位置:pos

  10. 操作纯删除:(pos<=k);则在pos之前的字符要全部删掉,然后剩余可操作的可利用单调栈来维护一个p[pos]打头,字典序最小的就是从最后开始往前删;

  11. 删除+旋转(n-pos<=k):对于一个p[i],删除+旋转或旋转+删除==删除;所以任一个p[i]要么删除、要么旋转;转完再删;等价于:先把pos之后的全删了,(因为比p[pos]大,转到前面去也不会更优),p[pos]转到第一个;后续操作和3.单调栈+末尾删除一样;

  12. #include<bits/stdc++.h>
    using namespace std;
    const int N=2e5+10;
    int p[N],n,k,pos=-1;
    vector<int> change1(){//纯删除
        vector<int> v1{n+1};
        if(pos<=k)
        {
            v1.clear();
            int res=k-pos;
            for(int i=pos;i<n;i++)
            {
                while(!v1.empty()&&p[i]<v1.back()&&res>0)
                {
                    res--;
                    v1.pop_back();
                }
                v1.push_back(p[i]);
            }
            while(res>0)//剩余次数未删的
            {
                v1.pop_back();
                res--;
            }
        }
        return v1;
    }
    vector<int> change2()//旋转+删除
    {
        vector<int>v2{n+1};
        if(n-pos<=k)
        {
            v2.clear();
            int res=k-(n-pos);//删除后还剩多少次
            for(int i0=0;i0<n;i0++)
            {
                int i=(i0+pos)%n;
                while(!v2.empty()&&p[i]<p[v2.back()]&&res>=(v2.back()<pos))
                {
                    res-=(v2.back()<pos);//在删除点之前的,每旋转过的,执行删除操作,在此之后再删等                                        //于直接删,所以只减1;
                    v2.pop_back();
                }
                v2.push_back(i);
            }
                while(res>=(v2.back()<pos))
        {
            res-=(v2.back()<pos);
            v2.pop_back();
        }
        for(auto &i:v2)
        {
            i=p[i];
        }
        
        }
        return v2;
    }
    int main()
    {
        cin>>n>>k;
        for(int i=0;i<n;i++)
        cin>>p[i];
        for(int i=0;i<n;i++)
        {
            if(i<=k||n-i<=k)
            {
                if(pos==-1||p[i]<p[pos])
                {
                    pos=i;
                }
            }
        }
        auto ans=min(change1(),change2());
        n=ans.size();
        for(int i=0;i<n;i++)
        {
            cout<<ans[i]<<' ';
        }
        cout<<endl;
    }
    在这里模拟一个单调堆栈:如果我们有剩余的操作,并且当前尾部大于要添加的下一个数字,我们只需擦除当前尾部即可。我们重复此操作,直到我们没有剩余的操作或当前尾部小于下一个数字。然后我们按下下一个数字。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到