Codeforces Round #674 (Div. 3)A-F题解

    科技2022-07-13  122

    Codeforces Round #674 (Div. 3)A-F题解

    比赛链接:https://codeforces.com/contest/1426

    A题 水题不写题解

    #include<bits/stdc++.h> #define ll long long #define llINF 9223372036854775807 #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); using namespace std; int32_t main() { IOS; int t; cin>>t; while(t--) { int n,x; cin>>n>>x; if(n<3) cout<<1<<endl; else cout<<(n-2)/x+((n-2)%x?1:0)+1<<endl; } }

    B题 简单构造

    题意为给定n个2 × \times × 2的矩阵,你可以使用任意次这些矩阵,要求拼出一个m × \times × m的按照主对角线对称的矩阵。询问是否可以构造。

    根据矩阵是否在主对角线上,我们可以注意到在对角线上的2 × \times × 2 矩阵也同时需要按照自己的主对角线对称,而不主对角线上的矩阵,两两之间需要互相对称,我们不难发现如果有满足主对角线上矩阵条件的2 × \times × 2 矩阵,那我们把这些矩阵直接铺满非对角线的部分也是满足要求的。

    因此直接判断给的2 × \times × 2 矩阵中是否存在自身就是个关于主对角线对称的矩阵即可,同时m需要满足是一个偶数。

    #include<bits/stdc++.h> #define ll long long #define llINF 9223372036854775807 #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); using namespace std; int num[107][2][2]; int32_t main() { IOS; int t; cin>>t; while(t--) { int n,m; cin>>n>>m; bool flag=0; for(int i=0;i<n;i++) { for(int j=0;j<2;j++) for(int k=0;k<2;k++) cin>>num[i][j][k]; if(num[i][0][1]==num[i][1][0]) flag=1; } if(m%2==0&&flag) cout<<"YES"<<endl; else cout<<"NO"<<endl; } }

    C题 简单构造,暴力或简单结论

    题意为一开始的时候数列只有一个数字1,每次操作你可以对数列中的一个数字加1,或者复制某一个数字到数列末尾,问你最少需要多少次操作可以使得这个数列中所有数的值加起来不小于n。

    我们注意到每次操作, 如果选择值+1的话只会对整体产生+1的影响,但是这个+1后的值会增加复制操作可增加的最大值。 而复制某一个值会增加一个当前最大的数的值,但是不会对后续操作可增加的最大值产生影响。

    我们直接贪心选择先不断对第一个数+1,+1到合适大小后不断复制这个值即可。 假设我们先+1到值为i,那么总的操作次数分为两个部分,第一个部分是+1的次数为i-1,第二个部分是复制数值i的次数,为n/i-1+n%i?1:0。 推到这里之后直接暴力for sqrt(n)已经可以过题。 但是还可以简单地再深入一下,我们对i增加1,会对第一部分的操作次数i-1产生+1的影响,那么在第二部分的n/i-1+n%i?1:0的部分至少要减去1,我们对i的+1操作才是正面的。写过《算竞》上余数之和这个例题可能会比较敏感,或者自己稿纸上写一下也能发现这个规律,此时i的值取sqrt(n)就是最优。当i>=sqrt(n)时,当i变化时,n/i的步长变化不可能大于1,与i<sqrt(n)的情况反过来对应。

    #include<bits/stdc++.h> #define ll long long #define llINF 9223372036854775807 #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); using namespace std; int32_t main() { IOS; int t; cin>>t; while(t--) { ll n; cin>>n; ll temp=sqrt(n); cout<<temp-1+n/temp-1+(n%temp?1:0)<<endl; } }

    D题 贪心,构造

    题意为给定一个数列,不包含0,现在问你最少需要往这个数列中插入几个数字,使得这个数列不存在某个连续的区间的数值和为0。

    这里直接采取贪心的策略就是了,从左往右扫过去,对于第一段出现的和为0的区间,我们必定要在中间插入一个数字,可以直接贪心在最右边的一个元素的左侧插入一个“无穷大”的值,这样的话,这个无穷大值左侧的部分都不会和右侧的部分加起来等于0了。

    这里区间为0可以用map记录前缀和的出现情况来处理,每次出现某个区间和为0后,插入次数+1并把map清零,记录当前位置的值到map里。

    #include<bits/stdc++.h> #define ll long long #define llINF 9223372036854775807 #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); using namespace std; int32_t main() { IOS; ll n,sum=0,ans=0; cin>>n; map<ll,bool>M;//M[x]记录前缀合x是否在前面出现过,如果当前前缀和为x,那么在当前位置到之前记录x的位置之间的数加起来为0 M[0]=1;//0要初始化为出现过,意思为开头到目前为止所有部分的值加起来为0 for(int i=0;i<n;i++) { ll x; cin>>x; sum+=x; if(M.find(sum)!=M.end())//如果当前前缀和和之前某个位置的相同,代表这两个位置间(包括当前位置)的值加起来为0 {//贪心过程在当前位置前的一个位置塞入一个绝对值足够大的数字即可 M.clear(); sum=x;//当前前缀合变为刚读入的这个数,前面的数字不再对后续产生影响,可以理解为我们在前面一个位置插入了一个无穷大 M[x]=M[0]=1; ans++; } else M[sum]=1; } cout<<ans<<endl; }

    E题 贪心,小结论,暴力

    两个人进行n轮石头剪刀布,每个人准备出几次剪刀,几次石头,几次布都已经给出,要求求出第一个人最少和最多能获得的胜场数。

    最多能获得的胜场数很容易贪心得到,直接第一个人的剪刀和第二个人的布的场次取较小值加到答案上,另外两种情况也相同。

    最少能获得的胜场数需要做个小结论,我们可以反过来思考,我们要获得最少的胜场数,也就等同于要得到最多的平局和负场数。而平局和负场的话,剪刀可以与剪刀构成平局,也可以去和石头构成负场。 我们优先考虑平局,再在这里我人为规定一个词叫做“被挤占”的状态,比如说1次第一个人的剪刀我们安排和第二个人的石头对应,1次第一个人的石头本来应当和第二个人的石头对应,现在“被挤占”后被迫和布对应。 如果剪刀石头布都存在“被挤占”的状态的话,不难发现构成了一个三场都负的循环,我们完全可以让他们变成三场都平局,排除这三个“被挤占”的状态。从而可以得到结论,最优的那种情况,必然可以转化为剪刀石头布中有一个不存在“被挤占”状态的方案,也就是优先考虑三种出法某一种。 由此我们直接for个3遍,优先考虑三种出法能获得的平局和负场数,取最大后被减去便是答案。

    #include<bits/stdc++.h> #define ll long long #define llINF 9223372036854775807 #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); using namespace std; ll a[3],b[3]; int32_t main() { IOS; ll n; cin>>n; cin>>a[0]>>a[1]>>a[2]; cin>>b[0]>>b[1]>>b[2]; ll ans=0;//记录最高可以得到的负场和平局数量 for(int i=0;i<3;i++)//优先考虑a中的哪一个 { ll now=0,sum=0;//now记录位置有多少场次需要寻找平局和负场,sum为优先考虑a[i]的情况下得到的负场和平局数 for(int j=0;j<3;j++) { int tar=(i-j+3)%3;//寻找当前位置的下标,现在是尽可能多的寻找平局和负场 ll temp=b[tar]; sum+=min(now,temp);temp-=min(now,temp);//上一个位置留下的部分可以在当前位置放 now=a[tar];//放置后上一个位置的场次没被安排的无法保存到下一个,直接清零记录为当前位置的场次即可 sum+=min(now,temp);now-=min(now,temp);//当前位置b仍然能安排的优先安排 } ans=max(ans,sum); } cout<<n-ans<<' '; ans=0; for(int i=0;i<3;i++) ans+=min(a[i],b[(i+1)%3]); cout<<ans<<endl; }

    F题 dp

    题意为给定一个字符串,只包含abc?四种字符,其中?字符可以用abc任意一个去替换,问将所有的?替换后的所有字符串中,子串abc(下标不一定连续)总共出现了几次。

    我们注意到,我们如果从左往右看过去的话,我们实际上需要关注的,只是a,ab,abc出现了几次。那么我们可以根据第i个位置是a,b,c做一个3 × \times × n 的线性dp。 首先当前位置的情况,要继承上个位置所有构造方案的情况(见trans函数)。之后根据当前位置构造是a还是b还是c进行三种不同的转换。 如果构造a的话,只会对a的数量产生影响,当前位置之前出现了x个问号,那么此时前面共有3x种构造方法,每种方法都会增加一个a。因此直接增加3x个a。 如果构造b的话,只会对ab的数量产生影响,增加前面一个位置下记录的所有a的个数即可。 如果构造c的话,只会对abc的数量产生影响,增加前面一个位置下记录的所有ab的个数即可。

    #include<bits/stdc++.h> #define ll long long #define llINF 9223372036854775807 #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); using namespace std; const ll mod=1e9+7; const ll maxn=2e5+7; struct Node { ll a,ab,abc; }; Node dp[maxn][3];//dp[i][j]表示第i个字符构造j的情况下,a/ab/abc各自出现了几次,j=0代表构造字符'a',j=1代表构造'b',j=2代表构造'c' //当s[i]是个确定的字符时,只转移到dp[i][0]上 int n; ll cas=1;//记录3^k,用于构造'a'时的转移 char s[maxn]; void trans(int x,int y) { for(int i=0;i<3;i++) { dp[x][y].a=(dp[x][y].a+dp[x-1][i].a)%mod; dp[x][y].ab=(dp[x][y].ab+dp[x-1][i].ab)%mod; dp[x][y].abc=(dp[x][y].abc+dp[x-1][i].abc)%mod; } } void transa(int x,int y)//构造a时的转移方式 { trans(x,y); dp[x][y].a=(dp[x][y].a+cas)%mod; } void transb(int x,int y)//构造b时的转移方式 { trans(x,y); dp[x][y].ab=(dp[x][y].ab+dp[x-1][0].a+dp[x-1][1].a+dp[x-1][2].a)%mod; } void transc(int x,int y)//构造c时的转移方式 { trans(x,y); dp[x][y].abc=(dp[x][y].abc+dp[x-1][0].ab+dp[x-1][1].ab+dp[x-1][2].ab)%mod; } int32_t main() { IOS; cin>>n>>(s+1); for(int i=1;i<=n;i++) { if(s[i]=='?') { transa(i,0); transb(i,1); transc(i,2); cas=cas*3%mod; } else { if(s[i]=='a') transa(i,0); if(s[i]=='b') transb(i,0); if(s[i]=='c') transc(i,0); } } cout<<(dp[n][0].abc+dp[n][1].abc+dp[n][2].abc)%mod<<endl; }
    Processed: 0.012, SQL: 8