Codeforces Round #566 (Div. 2) A~F个人题解

本文介绍了四道算法竞赛题目,涉及数论、搜索和贪心策略。A题关注L型瓷砖填充,B题探讨字符'+"在网格中的连接,C题研究诗歌的构造,D题解决无根树的特定根节点选择,E题求解特定指数幂,F题讨论正弦函数的最大值。每道题都提供了思路、解题关键和代码实现,适合提升算法思维能力。
摘要由CSDN通过智能技术生成

Dashboard - Codeforces Round #566 (Div. 2) - Codeforces

A. Filling Shapes

题意:给你一个3 \times n的表格,你要用小L型的瓷砖把整个表格填满,问你有多少种方法?

知识点:数学,思维

思路:通过一点点观察发现,想要满足宽度是3,一定是俩个L型瓷砖交叉这放,并且对于一个长度为2宽度为3的表格,只有两种放法,如下图

 所以只要表格长度为偶数,那么答案一定是2^n/2(有多少个长度为2的,每个有两种情况)

奇数一定不可能填满,因为没有长度为1的图形来填,也不可能凑出来。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =1e9+6;
void solve(){
    ll n;cin>>n;
    if(n&1)cout<<0<<'\n';//奇数没有
    else cout<<(1ll<<(n/2))<<'\n';//偶数 2^(n/2)个
}
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;//cin>>_;
    while(_--){
        solve();
    }
    return 0;
}
/*
*/

 B. Plus from Picture

题意:有一个w \times h大小的表格,表格上每一个位置只会出现两种字符,‘+’和‘.’,问是否所有的‘+’连接成了一个大的 + 号(所有‘+’字符都在这个大的 + 号上,大的 + 号四个延伸出去的边长度至少大于等于1,宽度等于1,中间不能是空的)?

知识点:搜索,思维

思路:我们先看每一个点是否可以是大 + 号中间那个点,如果是放入我们的数组里,然后先判断数组的大小,如果大小大于1或者等于0,就是说不满足所有的点都在大的 + 号上或者说 大的 + 号的边的宽度大于1,这些都是NO的,现在只剩下了数组大小等于1,我们搜4个方向上所能走的点打上标记,最后看整个图,如果存在没有打上标记的‘+’,说明不是所有的‘+’构成了大的 + 号,输出NO,否则输出 YES

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =1e9+6;
char mp[505][505];
ll n,m;
bool vis[505][505];
vector<pair<ll,ll> >vp;
ll dx[5]={0,0,0,1,-1};
ll dy[5]={0,1,-1,0,0};
bool check(ll x,ll y){
    for(int i=0;i<5;++i)if(mp[x+dx[i]][y+dy[i]]!='*')return false;
    return true;
}
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j){
            cin>>mp[i][j];
            if(mp[i][j]=='.')vis[i][j]=true;//先给'.'打上标记这样好判断一点
        }
    }
    for(int i=2;i<n;++i){
        for(int j=2;j<m;++j){
            if(check(i,j))vp.push_back({i,j});//如果可以当中间点,放入数组里
        }
    }
    if(vp.size()!=1)cout<<"NO\n";//数组大小不为1,一定不可以
    else {
        auto w=vp[0];
        vis[w.first][w.second]=true;//记得给初始点打标记
        for(int i=1;i<5;++i){//四个方向
            for(ll j=1;j<505;++j){//所能走的最长长度
                ll tx=w.first+dx[i]*j,ty=w.second+dy[i]*j;
                if(tx<1||ty<1||tx>n||ty>m||vis[tx][ty])break;
                //如果遇到打标记的点,说明遇到'.'了,这个方向就不用看了
                vis[tx][ty]=true;
            }
        }
        for(int i=1;i<=n;++i){
            for(int j=1;j<=m;++j){
                if(!vis[i][j]){//如果存在没有打标记的点,输出NO
                    cout<<"NO\n";
                    return ;
                }
            }
        }
        cout<<"YES\n";//都成立了,输出YES
    }
}
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;//cin>>_;
    while(_--){
        solve();
    }
    return 0;
}
/*
*/

 C. Beautiful Lyrics

题意:给你n个字符串,现在问你能从中选出的美好的诗句有几条?

美好的诗句满足:

(1)美好的诗句由4个字符串组成,分为左上,左下,右上,右下。

(2)左上和左下的元音字母个数要相同。

(3)右上和右下的元音字母个数要相同,并且右上和右下的最后一个元音字母一样。

(元音字母只包括 a,e,i,o,u)

知识点:贪心,排序

思路:发现条件(3)是包含条件(2)的,所以肯定是条件(3)满足的越多越好,因为多余的可以退化成满足(2),所以现在问题转化成了问有多少个满足条件(3)的字符串对和剩下的有多少个满足条件(2)的字符串对。这个我们可以先按元音字母个数从小到大排序,再按最后一个元音字母的大小从小到大排序,然后按要求找就OK了,具体实现可以看代码。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =1e9+6;
struct dd{
    ll cnt,last;//元音字母个数,最后一个元音字母
    string s;//这个字符串是什么
    inline bool operator <(const dd &r)const{
        if(cnt==r.cnt)return last<r.last;//再按最后一个元音字母从小到大排序
        return cnt<r.cnt;//先按元音字母个数从小到大排序
    }
}op[N];
bool vis[N];//打标记用
void solve(){
    ll n;cin>>n;
    for(int i=1;i<=n;++i){
        string s;cin>>s;
        ll cnt=0,last=0;
        for(int i=0;i<s.length();++i){
            if(s[i]=='a'||s[i]=='e'||s[i]=='i'||s[i]=='o'||s[i]=='u'){
                cnt++;
                last=s[i]-'a';
            }
        }
        op[i]={cnt,last,s};
    }
    sort(op+1,op+n+1);
    vector<pair<string,string> >ansr,ansl;//记录答案,r是满足(3),l是满足(2)
    ll pre=-1,precnt=-1,preid=0;//上一个状态的最后一个元音字母,元音字母个数,下标
    for(int i=1;i<=n;++i){
        if(pre==op[i].last&&precnt==op[i].cnt){//如果满足条件(3)
            ansr.push_back({op[i].s,op[preid].s});//先扔进去
            vis[i]=true;//这个下标已经用过了,打标记
            vis[preid]=true;//这个下标已经用过了,打标记
            pre=-1;precnt=-1;preid=0;//这俩都已经用了,说明没有上一个
        }
        else {//不满足,更新
            pre=op[i].last;
            precnt=op[i].cnt;
            preid=i;
        }
    }
    //满足条件(3)的找完了,找满足条件(2)的
    pre=-1;precnt=-1;preid=0;//上一个状态的最后一个元音字母,元音字母个数,下标
    for(int i=1;i<=n;++i){
        if(vis[i])continue;//用过,说明不能用了
        if(precnt==op[i].cnt){//条件(2)只用满足元音字母个数相同
            ansl.push_back({op[i].s,op[preid].s});//先扔进去
            pre=-1;precnt=-1;preid=0;//这俩都已经用了,说明没有上一个
        }
        else {//不满足,更新
            pre=op[i].last;
            precnt=op[i].cnt;
            preid=i;
        }
    }
    ll lsize=ansl.size(),rsize=ansr.size();
    if(lsize>=rsize){//如果满足条件(2)的比满足条件(3)的多
        cout<<rsize<<'\n';
        for(int i=0;i<rsize;++i){
            cout<<ansl[i].first<<' '<<ansr[i].first<<'\n';
            cout<<ansl[i].second<<' '<<ansr[i].second<<'\n';
        }
    }
    else {//如果满足条件(2)的比满足条件(3)的少,因为满足条件(3)可以退化成满足条件(2)的
        //个数就是
        ll tmp=(rsize-lsize)/2;
        for(int i=rsize-1;i>=rsize-tmp;--i){//然后把他们扔过去
            ansl.push_back(ansr[i]);
        }
        cout<<lsize+tmp<<'\n';
        for(int i=0;i<lsize+tmp;++i){
            cout<<ansl[i].first<<' '<<ansr[i].first<<'\n';
            cout<<ansl[i].second<<' '<<ansr[i].second<<'\n';
        }
    }
}
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;//cin>>_;
    while(_--){
        solve();
    }
    return 0;
}
/*
*/

D. Complete Mirror

题意:给你一颗有n个结点的无根树,问你是否能找到一个点作为根满足:

对于任意两点v_1,v_2满足dis(root,v_1)=dis(root,v_2)并且deg(v_1)=deg(v_2)

(dis(i,j)表示点i到点j之间的边的数量,deg(i)表示点i和多少个点直接相连)

找到输出点的编号,找不到输出-1

知识点:DFS,树的重心

思路:如果暴力的想,就是n个点都可能成为根,每个都判一次,复杂度O(n^2)肯定是不可以的,所以想尽可能减少判的次数,发现有一些点是没有判的必要的,所以现在就是考虑哪些点可能成为根

先考虑条件,这两个条件要都满足一定是这个点孩子的子树都是同构的,而想要满足这个条件,那么这个点可能在树的重心位置上,所以重心是可能成为根的一个点,然后考虑重心的孩子,如果孩子的子树是一个树型的,那这个子树上的所有点都不可能是根,因为上面所有点都不可能满足条件的。如果孩子的子树一个链型的,那这个子树的叶子是可能成为根的,但其实这里不用全判,如果俩个链长度相同,取其一就行,毕竟是同构的,所以我们要判断的就很少了。

(这道题其实多动手画图就能发现了)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =1e9+7;
ll n;
ll siz[N],weight[N];//求重心用的
ll treefocus[2];//树的重心下标
vector<ll>mp[N];//存树的边
void dfs(ll u,ll root){//求树的重心
    siz[u]=1;weight[u]=0;
    for(auto v:mp[u]){
        if(v==root)continue;
        dfs(v,u);
        siz[u]+=siz[v];
        weight[u]=max(weight[u],siz[v]);
    }
    weight[u]=max(weight[u],n-siz[u]);
    if(weight[u]<=n/2){//根据定义判断树的重心
        treefocus[treefocus[0]!=0]=u;
    }
}
vector<ll>ans;//要判断的数组
ll d[N],ye,Maxdeep;//度数,叶子的下标,链的长度
bool tp,in[N];//是不是链,有没有放过
void DFS(ll u,ll root,ll deep){
    ll ge=1;//孩子个数
    Maxdeep=max(Maxdeep,deep);
    for(auto v:mp[u]){
        if(v==root)continue;
        DFS(v,u,deep+1);
        ge--;
    }
    if(ge==1)ye=u;//叶子没有孩子
    if(ge<0)tp=false;//个数小于0了,说明不是链
}
bool flag;
ll op[N],maxdeep;
void check(ll now,ll root,ll deep){//判断是不是满足条件
    maxdeep=max(maxdeep,deep);
    if(op[deep]){//同一深度,度数要相等
        if(d[now]!=op[deep])flag=false;
    }
    else op[deep]=d[now];
    for(auto v:mp[now]){
        if(v==root)continue;
        check(v,now,deep+1);
    }
}
void solve(){
    cin>>n;
    for(ll i=1,u,v;i<n;++i){
        cin>>u>>v;
        mp[u].push_back(v);
        mp[v].push_back(u);
        d[u]++;d[v]++;
    }
    dfs(1,-1);//求树的重心
    ans.push_back(treefocus[0]);//放入要判断的数组中
    for(auto v:mp[ans[0]]){//判断重心的孩子
        tp=true;Maxdeep=0;
        DFS(v,ans[0],1);
        if(tp){
            if(in[Maxdeep])continue;//如果这个长度的链已经放入要判断的数组了,就不用放了
            in[Maxdeep]=true;
            ans.push_back(ye);
        }
    }
    for(auto w:ans){
        flag=true;
        check(w,-1,1);
        for(int i=1;i<=maxdeep;++i)op[i]=0;
        if(flag){//满足
            cout<<w<<'\n';//输出满足的下标
            return ;
        }
    }
    cout<<-1<<'\n';//没有满足的
}
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;//cin>>_;
    while(_--){
        solve();
    }
    return 0;
}
/*
*/

E. Product Oriented Recurrence

题意:f_x=c^{2x-6}\times f_{x-1}\times f_{x-2}\times f_{x-3} ,4\leq x,已知f_1,f_2,f_3,c,n的值,求f_n?

知识点:矩阵快速幂,欧拉定理

思路:发现f_n最后一定等于c^{x_1}\times f_1^{x_2}\times f_1^{x_3}\times f_1^{x_4},所以我们可以构造一个关于这几项幂的矩阵,然后矩阵快速幂加速,我是通过分别构造c的矩阵和f_1,f_2,f_3的矩阵来写的。

\begin{bmatrix} F_{n,1} & F_{n,2} & F_{n,3} \\ F_{n-1,1} & F_{n-1,2} & F_{n-1,3} \\ F_{n-2,1} & F_{n-2,2} & F_{n-2,3} \end{bmatrix}={ \begin{bmatrix} 1 & 1 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \end{bmatrix} }^{n-3}\times \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}

其中F_{n,1},F_{n,2},F_{n,3}分别表示答案中f_3,f_2,f_1的多少次幂

\begin{bmatrix} C_n \\ C_{n-1} \\ C_{n-2}\\ n\\ 1 \end{bmatrix} = {\begin{bmatrix} 1&1&1&2&-4 \\ 1 & 0 & 0&0&0 \\ 0 & 1 & 0&0&0\\ 0&0&0&1&1\\ 0&0&0&0&1 \end{bmatrix}}^{n-3}\times \begin{bmatrix} 0\\ 0\\ 0\\ 3\\ 1 \end{bmatrix}

其中C_n表示答案中c的多少次幂

最后输出c^{C_n}\times f_3^{F_{n,1}}\times f_2^{F_{n,2}}\times f_1^{F_{n,3}}\mod 1e9+7就可以了(矩阵里是对phi(1e9+7)取模,因为欧拉定理)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =1e9+6;
const ll Mod =1e9+7;
const int maxl=105;
struct Matrix {
    ll a[maxl][maxl];
    int n,m;
    inline Matrix(int n,int m) : n(n) , m(m) {
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j)a[i][j]=0;
        }
    }
    inline void init(){
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j)a[i][j]=0;
            a[i][i]=1;
        }
    }
    inline void Clear(){
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j)a[i][j]=0;
        }
    }
    inline Matrix operator * (const Matrix &t){
        Matrix io(n,t.m);
        for(int i=0;i<n;++i){
            for(int j=0;j<t.m;++j){
                for(int k=0;k<m;++k){
                    io.a[i][j]=(io.a[i][j]+a[i][k]*t.a[k][j]%mod+mod)%mod;
                }
            }
        }
        return io;
    }
    inline Matrix operator + (const Matrix &t){
        Matrix io(n,m);
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j){
                io.a[i][j]=(a[i][j]+t.a[i][j]+mod)%mod;
            }
        }
        return io;
    }
    inline Matrix ksm(ll b){
        Matrix ans(this->n,this->m),a=*this;
        ans.init();
        while(b){
            if(b&1)ans=ans*a;
            a=a*a;
            b>>=1;
        }
        return ans;
    }
};
ll ksm(ll a,ll b){
    ll ans=1;
    while(b){
        if(b&1)ans=ans*a%Mod;
        a=a*a%Mod;
        b>>=1;
    }
    return ans;
}
ll f[3],n,c;
void solve(){
    cin>>n;
    for(int i=2;i>=0;--i)cin>>f[i];//倒着输入对标一下
    cin>>c;
    Matrix now(3,3);
    for(int i=0;i<3;++i)now.a[0][i]=1;//构造f1,f2,f3
    now.a[1][0]=now.a[2][1]=1;//        矩阵      矩阵里是对phi(1e9+7)取模
    now=now.ksm(n-3);
    ll ans=1;
    for(int i=0;i<3;++i){
        f[i]=ksm(f[i],now.a[0][i]);
        ans=ans*f[i]%Mod;//更新答案
    }
    Matrix cl(5,5);
    for(int i=0;i<3;++i)cl.a[0][i]=1;                        //构造c
    cl.a[0][3]=2;cl.a[0][4]=mod-4;                           //的
    cl.a[1][0]=cl.a[2][1]=cl.a[3][3]=cl.a[3][4]=cl.a[4][4]=1;//矩阵
    cl=cl.ksm(n-3);
    ll w=(cl.a[0][3]*3ll%mod+cl.a[0][4])%mod;//对phi(1e9+7)取模,因为是次幂
    cout<<ans*ksm(c,w)%Mod<<'\n';//输出答案
}
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;//cin>>_;
    while(_--){
        solve();
    }
    return 0;
}
/*
*/

F. Maximum Sine

题意:给你a,b,p,q四个整数,问你函数y=|sin(\frac{p}{q}\pi x)|a\leq x\leq b中找到最小的x让y最大?

知识点:类欧几里德,扩展欧几里得(需要一定的数论基础

思路:根据一点点高中知识,可以知道|sin(x)|x=(k+\frac{1}{2})\pi,k\in Z时候取到最大值,所以我们要\frac{p}{q}\pi x=(k+\frac{1}{2})\pi

\frac{px}{q}-\frac{1}{2}=k也就是\frac{2px-q}{2q}=k等价于2px\mod2q-q尽可能趋近0或者2q,

我们可以考虑二分2px\mod2q对q的偏差,

我们首先要知道一个定理:[x\mod p \in [l,r]]等价于\left \lfloor \frac{x-l}{p} \right \rfloor-\left \lfloor \frac{x-r-1}{p} \right \rfloor

这个也很好验证:如果0\leq x< l,后面那个等于-1-(-1)=0

如果l\leq x\leq r,后面那个等于0-(-1)=1

如果r<x<p,后面那个等于0-0=0

如果存在x满足2px\mod2q \in [l,r],那么一定满足\sum_{x=a}^{b}\frac{2px-l}{2q}-\frac{2px-r-1}{2q}\geq 0

后面这个其实就是一个类欧几里德的东西,(类欧几里德就不展开讲了,这里就用了个板子)然后我们就能二分出来2px\mod2q对q的最小偏差d

也就是我们知道2px\mod 2q =q-d2px\mod 2q =q+d

现在我们要还原x的值,发现这俩个就是个同余方程,我们用扩展欧几里得解一下就好了

2px+2qy=q+d

2px+2qy=q-d

还原出来x记得要把x再更新到[a,b]这个区间里,取两者的最小值就行了

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1e5+5;
const ll mod =1e9+7;
ll calc(ll a,ll b,ll c,ll n){//类欧几里德
    if(n<0)return 0;
    if(n==0)return b/c;
    if(a>=c||b>=c)return n*(n+1)/2*(a/c)+(n+1)*(b/c)+calc(a%c,b%c,c,n);
    ll m=(a*n+b)/c;
    return m*n-calc(c,c-b-1,a,m-1);
}
ll a,b,p,q,Q,P,x,y,g;
bool check(ll l,ll r){
    return calc(P,Q-l,Q,b)-calc(P,Q-l,Q,a-1)-calc(P,Q-r-1,Q,b)+calc(P,Q-r-1,Q,a-1);
                //这里的Q-l和Q-r-1和l,r-1是一样的,只不过防止了负数
}
ll exgcd(ll a,ll b,ll &x,ll &y){//扩展欧几里得
    if(!b){x=1;y=0;return a;}
    ll g=exgcd(b,a%b,x,y);
    ll tmp=x;x=y;
    y=tmp-(a/b)*y;
    return g;
}
void solve(){
    cin>>a>>b>>p>>q;
    Q=q<<1;P=p<<1;
    ll l=0,r=q,mid;
    while(l<=r){//二分偏差
        mid=l+r>>1;
        ll L=q-mid,R=q+mid;//偏差后的两个值
        if(check(L,R))r=mid-1;
        else l=mid+1;
    }
    g=exgcd(P,Q,x,y);
    ll ans=1e18;//默认最大值
    if((q-l)%g==0){
        ll t=Q/g,now=(x*((q-l)/g)%t+t)%t;
        while(now>=a)now-=t;//限制成最小
        while(now<a)now+=t;//限制
        ans=min(ans,now);
    }
    if((q+l)%g==0){
        ll t=Q/g,now=(x*((q+l)/g)%t+t)%t;
        while(now>=a)now-=t;//限制成最小
        while(now<a)now+=t;//限制
        ans=min(ans,now);
    }
    cout<<ans<<'\n';
}
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int _=1;cin>>_;
    while(_--){
        solve();
    }
    return 0;
}
/*
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值