cf #572 div2 solution (RW personal)

本文深入解析CF572Div2竞赛题目,涵盖从简单数学题到复杂算法题的全面解答,包括代码实现细节及关键思路讲解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

cf #572 div2 my solution

A

水题

#include<bits/stdc++.h>
using namespace std;

const int maxn = 2e5+10;
const int mod = 1e9+7;

int solve(string s)
{
    int num=0;
    for(int i=0;i<s.length();++i){
        if(s[i]=='1') num++;
    }
    return num;
}

int main()
{
    int n;
    cin>>n;
    string s;
    cin>>s;
    if(solve(s) != n - solve(s)){
        cout<<1<<endl;
        cout<<s<<endl;
    }
    else{
        cout<<2<<endl;
        cout<<s[0]<<" "<<s.substr(1)<<endl;
    }
    return 0;
}

B

排序,构造一下即可

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1e5+10;

int a[maxn];

int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;++i) cin>>a[i];
    sort(a,a+n);
    if(a[n-1]>=a[n-2]+a[n-3]){
        cout<<"NO"<<endl;
    }
    else{
        cout<<"YES"<<endl;
        cout<<a[n-3]<<" "<<a[n-1]<<" "<<a[n-2];
        for(int i=n-4;i>=0;--i) cout<<" "<<a[i];
        cout<<endl;
    }
    return 0;
}

上面的方法要进行排序,思考下面问题

在这里插入图片描述

C

注意到所有区间最后会化为一个数,过程中每有大于等于10的数,计数+1

所以可以直接将区间求和,下取整除10即可。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1e5+10;
const int duan = 32*(1<<1)-(1<<5);
int n;
int a[maxn];
int pre[maxn];

void init()
{
    pre[1] = a[1];
    for(int i=2;i<=n;++i) pre[i] = pre[i-1] + a[i];
}

int main()
{
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>a[i];
    }
    init();
    int q;
    cin>>q;
    while(q--)
    {
        int l,r;
        cin>>l>>r;
        cout<<(pre[r] - pre[l-1] )/10<<endl;
        //cout<<l<<" "<<r<<endl;
    }
    return 0;
}

D1

直观感觉,有度数为2的点,就不能任意构造,因为该点相连的两个边的权值一定相同。

要证明的是,为什么只要没有度数为2的点,就能构造出任意权值组合。见官方题解

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1e5+10;

int n;
int du[maxn];

int main()
{
    ios::sync_with_stdio(false);
    cin>>n;
    int nn=n;
    int a,b;
    n--;
    while(n--)
    {
        cin>>a>>b;
        du[a]++;
        du[b]++;
    }
    bool flag=0;
    for(int i=1;i<=nn;++i){
        if(du[i]==2){
            cout<<"NO"<<endl;
            return 0;
        }
    }
    cout<<"YES"<<endl;
    return 0;
}

D2

给你一颗无根树,边权都不相同(且均为偶数),问如何通过每次选两个叶子节点并对路径+数,来构造所有边权.

注意D1中+的数可以是任意实数,而D2是整数.

首先,我们证明D1中为什么可以任意构造(只要度不为2),

一个性质:对于树中的任意一个点vvv到一个叶子节点uuu的路径,我可以只改变他们的权值(使之都+x),而其他边上的权值都不变.具体来说,有两种情况.

  1. v是另外一个叶子节点,显然满足

  2. v不是叶子节点,则其度数一定>2,(2不可能,因为直接令相关的两条边权值不同,就构造不了)

    由于其度数>2,则至少对应3颗子树. 我们分别找三颗子树中的三个叶子,记为l1,l2,ul_1, l_2, ul1,l2,u.

    则对u−&gt;l1u-&gt;l_1u>l1 加上x/2x/2x/2 , 对u−&gt;l2u-&gt;l_2u>l2加上x/2x/2x/2, l1−&gt;l2l_1-&gt; l_2l1>l2减去x/2x/2x/2 ,

    最后相当于对u−&gt;vu-&gt;vu>v 加上了xxx . #

有了上面这个性质,继续说明如何构造任意情况.

在这里插入图片描述

solve(v):

if vvv doesn’t have sons, then return.

otherwise, for each son uuu we wiil find a leaf in subtree of uuu — let’s name it www. Than, add auva_{uv}auv on the path
vwvwvw and recalculate needed numbers on the edge in this path(it means for each edge eee on the path make this ae−=auva_e -= a_{uv}ae=auv),
after it, call solve(uuu).

非要这样递归维护下面的边权吗? 其实一条边,可以直接用两次上述性质做个差分(注意到,能用性质的前提是权值为偶数)即可凑成.(差一条边的两条路径)

所以先预处理出每个点的叶子节点即可.时间复杂度O(n)O(n)O(n).

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1e3+10;

int a[maxn],root;// fa[root] = 0
pair<int,int> b[maxn];//two leaf for every node
vector<pair<int,int>> G[maxn];

void dfs(int u, int fa){
    if(G[u].size()==1 && fa){// leaf  返回自己的编号
        b[u].first = u;//自己指向自己
        b[u].second = -1;//标记
        return ;
    }
    for(int i=0;i<G[u].size();++i){
        int v = G[u][i].first;
        if(v == fa) continue;
        dfs(v, u);
        if(!b[u].first) b[u].first = b[v].first;
        else if(!b[u].second) b[u].second = b[v].first;
    }
}

void dfs2(int u, int fa){
    if(G[u].size()==1) return ;
    for(int i=0;i<G[u].size();++i){
        if(G[u][i].first == fa) continue;
        int v = G[u][i].first;
        int val = G[u][i].second;
        //u->v val
        int l1 = b[u].first;
        int l2 = b[u].second;
        assert(l1!=l2);
        if(b[v].first == l1 || b[v].first == l2){
            //将l1 或 l2换掉
            if(u == root){//如果是根至少有三个值 一定能找到
                for(int j=0;j<G[u].size();++j){
                    if(b[G[u][j].first].first!= l1 && b[G[u][j].first].first!=l2){
                        if(b[v].first == l1) l1 = b[G[u][j].first].first;
                        if(b[v].first == l2) l2 = b[G[u][j].first].first;
                        break;
                    }
                }
            }
            else{//u不是根 则fa一定有另一个兄弟 取他的叶子
                for(int j=0;j<G[fa].size();++j){
                    if(G[fa][j].first ==u ) continue;
                    if(b[G[fa][j].first].first != l1 && b[G[fa][j].first].first != l2){
                        if(b[v].first == l1) l1 = b[G[fa][j].first].first;
                        if(b[v].first == l2) l2 = b[G[fa][j].first].first;
                        break;
                    }
                    if(b[G[fa][j].first].second != l1 && b[G[fa][j].first].second != l2){
                        if(b[v].first == l1) l1 = b[G[fa][j].first].second;
                        if(b[v].first == l2) l2 = b[G[fa][j].first].second;
                        break;
                    }
                }
            }
        }
        cout<<b[v].first<<" "<<l1<<" "<<val/2<<endl;
        cout<<b[v].first<<" "<<l2<<" "<<val/2<<endl;
        cout<<l1<<" "<<l2<<" "<<-val/2<<endl;
        //v 不是叶子
        if(G[v].size()!=1){
            //取 l1 b[v].first  和 b[v].second
            cout<<b[v].first<<" "<<l1<<" "<<-val/2<<endl;
            cout<<b[v].first<<" "<<b[v].second<<" "<<-val/2<<endl;
            cout<<l1<<" "<<b[v].second<<" "<<val/2<<endl;
        }
        dfs2(v,u);
    }
}

int main()
{
    //freopen("in.txt","r",stdin);
    ios::sync_with_stdio(false);
    int n,leanum=0;
    cin>>n;
    for(int i=0;i<n-1;++i){
        int u,v,val;
        cin>>u>>v>>val;
        G[u].push_back(make_pair(v,val));
        G[v].push_back(make_pair(u,val));
    }
    for(int i=1;i<=n;++i) if(G[i].size()==2) {
        cout<<"NO"<<endl;
        return 0;
    }
    if(n==2){
        cout<<"YES"<<endl;
        cout<<"1"<<endl;
        cout<<"1 2 "<<G[1][0].second<<endl;
        return 0;
    }
    //找一个du>1的node作为根
    for(int i=1;i<=n;++i) if(G[i].size()>2){root = i ;break;}
    dfs(root, 0);
    //for(int i=1;i<=n;++i) cout<<b[i].first<<" "<<b[i].second<<endl;
    for(int i=1;i<=n;++i) if(G[i].size()==1) leanum++;
    cout<<"YES"<<endl;
    cout<<6*(n-1) - 3*leanum<<endl;
    dfs2(root, 0);
    return 0;
}

这题写的我很难受,估计写复杂了. 我是先记录下每个点可能的两个叶子 (属于不同的子树)

然后各种判 先这样把 不改了…

E

find how many pairs satisfy below :
(ai+aj)(ai2+aj2)≡k&VeryThinSpace;mod&VeryThinSpace;p (a_i + a_j)(a_i^2 + a_j^2) \equiv k \bmod p (ai+aj)(ai2+aj2)kmodp

比赛的时候不会。看题解发现是个水题 /(ㄒoㄒ)/~~ 数学直觉不好

问题的难点在于转化,于是往原根方向想,可是没有思路。

实际上,只要两边同乘以ai−aja_i - a_jaiaj就可以继续操作了。

ai4−aj4≡k∗(ai−aj)a_i^4 - a_j^4 \equiv k*(a_i - a_j)ai4aj4k(aiaj)

ai4−k∗ai≡aj4−k∗ajmod pa_i^4 - k*a_i \equiv a_j^4 - k*a_j \quad mod \ pai4kaiaj4kajmod p

发现式子两边都只和一个数有关了 ,于是对之前的aaa数组做对应的操作后,看有多少数对在mod pmod \ pmod p的意义下是相同的即可。 用map即可

#include<bits/stdc++.h>
using namespace std;

const int maxn = 3e5+10;

int n,k,p;

int a[maxn];
unordered_map<int, int> mp;
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>p>>k;
    for(int i=0;i<n;++i){
        long long t;
        cin>>t;
        a[i] = t*t%p*t%p*t%p;
        a[i] = ((a[i] - k*t)%p + p)%p;
    }
    long long ans=0;
    for(int i=0;i<n;++i){
        int t = mp[a[i]];
        ans+=t;
        mp[a[i]] = t+1;
    }
    cout<<ans<<endl;
    return 0;
}

F

题目意思是,给你一个数组a[],那么他又很多子序列对吧.

我们定义一个子序列的价值是, min∣ai−aj∣, i≠jmin|a_i -a_j|, \ i \neq jminaiaj, i̸=j

求数组中所有长度为kkk的子序列的价值和

数组长度1e3 ,值域1e5

思路

考虑怎么转化这个问题, 发现这个价值是变得,不太好dp

我们定义pip_ipi 为数组中,价值$\geq i $的子序列数目(即至少为i),

那么,可以说p1+p2+p3+...+pmax(a)p_1 + p_2 + p_3 +...+p_{max(a)}p1+p2+p3+...+pmax(a) 即为所求. (没有P0P_0P0,是因为其)

为什么呢?有一种前缀和的思想在里面.

比如数组中有一些子序列的价值为3,那么他们最后对答案的贡献是 数量*3

看上面的式子,p1,p2,p3p_1 , p_2, p_3p1,p2,p3 中肯定包含有这些价值为3的序列. 也是3个 (对其他价值的序列同理)

那现在还有两个问题:

  1. 如何求pip_ipi

    dp[i][j]dp[i][j]dp[i][j] 表示以第iii个数结尾,长度为jjj 且满足价值至少为limitlimitlimit 的序列数量.

    转移: 从dp[j−1,j,...i−1][j−1]dp[j-1,j,...i-1][j-1]dp[j1,j,...i1][j1] 转移过来 第一感觉时间复杂度O(n2k)O(n^2 k )O(n2k)

    考虑对原数组排序,不影响答案.(原先要任意挑出一对 , 排序前 排序后看成双射即可)

    如果数组是有序的,对于dp[..i−1,i,i+1..][j]dp[..i-1,i,i+1..][j]dp[..i1,i,i+1..][j] 他们的转移有公共部分.

    具体来说,是单调的 所以对同一列的dpdpdp值,可以维护一个now指针,表示最靠右的数 且满足a[now]+limit&lt;=a[i]a[now]+limit&lt;=a[i]a[now]+limit<=a[i]

    当i+1了,再尽量让now变大 , 时间复杂度为O(nk)O(nk)O(nk)

  2. 时间复杂度

    再加上一开始的Limit的枚举,复杂度为O(max(a)∗n∗k)O(max(a)*n*k)O(max(a)nk)

    考虑真的要dpdpdp max(a)max(a)max(a) 次吗?

    假设当前limit=xlimit = xlimit=x, 则从a[1]a[1]a[1]a[k]a[k]a[k] 至少增加了(k−1)∗x(k-1)*x(k1)x 而对于数组aaa ,提供的最大增量为a[n]−a[1]a[n]-a[1]a[n]a[1] (排序后). 因此只要对(k−1)∗x≤a[n]−a[1](k-1) *x \leq a[n] - a[1](k1)xa[n]a[1]的那些xxx ,求pxp_xpx 而更大的xxx ,显然px=0p_x = 0px=0 .

    时间复杂度为O(max(a)(k−1)∗nk)=O(max(a)∗n)O({max(a) \over (k-1)} * nk) = O(max(a) * n)O((k1)max(a)nk)=O(max(a)n) 可以跑完.

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int k,n;
const int maxn = 1e3+10;
const int mod = 998244353;
int a[maxn];
int dp[maxn][maxn];//dp[i][len] 以第i个数结尾 长度为len的序列 (且满足价值最小比某个下界大于等于)的数量

int solve(int limit)
{
    for(int j=2;j<=k;++j){//后面每一列
        int now_v = 0, now_i = j-2;
        for(int i=j;i<=n;++i){
            while( a[now_i+1]+limit<=a[i]  && now_i+1 < i){//先维护now_i 看最右能到哪
                now_i++;
                now_v=now_v + dp[now_i][j-1];
                if(now_v > mod) now_v-=mod;
            }
            dp[i][j]=now_v;
        }
    }
    int ans = 0;
    for(int i=k;i<=n;++i) ans = (ans + dp[i][k] >= mod) ? ans+dp[i][k] - mod : ans + dp[i][k];
    return ans;
}

int main()
{
    //freopen("in.txt","r",stdin);
    ios::sync_with_stdio(false);
    cin>>n>>k;
    for(int i=1;i<=n;++i) cin>>a[i];
    sort(a+1,a+1+n);
    int ans = 0;
    for(int i=1;i<=n;++i) dp[i][1] = 1;    //第一列
    for(int i=1;i<=(a[n] - a[1])/(k-1);++i) ans = (ans + solve(i))%mod;
    cout<<ans<<endl;
    return 0;
}
资源下载链接为: https://pan.quark.cn/s/22ca96b7bd39 在当今的软件开发领域,自动化构建与发布是提升开发效率和项目质量的关键环节。Jenkins Pipeline作为一种强大的自动化工具,能够有效助力Java项目的快速构建、测试及部署。本文将详细介绍如何利用Jenkins Pipeline实现Java项目的自动化构建与发布。 Jenkins Pipeline简介 Jenkins Pipeline是运行在Jenkins上的一套工作流框架,它将原本分散在单个或多个节点上独立运行的任务串联起来,实现复杂流程的编排与可视化。它是Jenkins 2.X的核心特性之一,推动了Jenkins从持续集成(CI)向持续交付(CD)及DevOps的转变。 创建Pipeline项目 要使用Jenkins Pipeline自动化构建发布Java项目,首先需要创建Pipeline项目。具体步骤如下: 登录Jenkins,点击“新建项”,选择“Pipeline”。 输入项目名称和描述,点击“确定”。 在Pipeline脚本中定义项目字典、发版脚本和预发布脚本。 编写Pipeline脚本 Pipeline脚本是Jenkins Pipeline的核心,用于定义自动化构建和发布的流程。以下是一个简单的Pipeline脚本示例: 在上述脚本中,定义了四个阶段:Checkout、Build、Push package和Deploy/Rollback。每个阶段都可以根据实际需求进行配置和调整。 通过Jenkins Pipeline自动化构建发布Java项目,可以显著提升开发效率和项目质量。借助Pipeline,我们能够轻松实现自动化构建、测试和部署,从而提高项目的整体质量和可靠性。
### 关于 Codeforces CF994 Div. 2 的题目与解答 #### 题目概述 Codeforces Round #412 (Div. 2),即 CF994,采用动态评分机制。这种机制意味着一个问题的最大分值取决于解决问题的人数与总参赛人数的比例[^1]。 #### 动态评分机制解释 对于该轮比赛而言,如果某道题目的解决者数量占总参与者的比例较低,则这道题目的分数会相对较高;反之则低。所有至少提交了一次代码的人都被视为参加了这场比赛。 #### 示例解法展示 考虑到不同的算法挑战,在这里提供一道关于字符串处理的问题及其解决方案作为例子: ##### 不同字符计数问题 给定一个长度不超过 \(10^5\) 的字符串,目标是计算其中不同字符的数量并输出重复字符的次数。以下是实现这一功能的一个 C++ 程序片段: ```cpp #include<bits/stdc++.h> using namespace std; const int N=100000+10; char a[N]; int main(){ int n; while(~scanf("%d",&n)){ scanf("%s",a); sort(a,a+n); int x=unique(a,a+n)-a; // 计算不重复字符数目 if(n>26) printf("-1\n"); else printf("%d\n",n-x); // 输出重复字符个数 } return 0; } ``` 此程序通过 `sort` 函数对输入字符串进行了排序,并利用 STL 中的 `unique()` 来去除相邻相同的元素,从而统计出独一无二的字符数量[^2]。 #### 构建三维结构体模型 另一个有趣的案例涉及构建由立方体组成的二维网格表示的物体。每个位置上的整数值代表堆叠在此处的小方块的高度。为了重建这个对象的外观视角下的形态,可以按照如下方法操作: ```cpp #include<bits/stdc++.h> using namespace std; const int N=107; int n,m,h,mp[N][N],a[N],b[N],i,j,k; int main(){ for(scanf("%d%d%d",&n,&m,&h),i=1;i<=m;++i){ scanf("%d",a+i); } for(i=1;i<=n;++i){ scanf("%d",b+i); } for(i=1;i<=n;++i){ for(j=1;j<=m;++j){ scanf("%d",&mp[i][j]); if(mp[i][j]){ mp[i][j]=min(a[j],b[i]); // 取主视图和侧视图高度较小者 } } } for(i=1;i<=n;++i,puts("")){ for(j=1;j<=m;++j){ printf("%d ",mp[i][j]); } } } ``` 这段代码接收了两组数据——分别对应每一列以及每一行的最大可能高度限制,并据此调整实际放置的立方体高度以满足视觉效果的要求[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值