Codeforces 1037 Div3(ABCDEF)

前言

前四题有多顺利E题就有多痛苦。

一、A. Only One Digit

#include 
using namespace std;

typedef long long ll;
typedef pair pii;

void solve()
{
    string x;
    cin>>x;

    int mn=10;
    for(int i=0;i>t;
    while(t--)
    {
        solve();    
    }
    return 0;
}

这个还是贪心,观察可得要找的这个数字y就是数字x里最小的数字。那就可以考虑将数字x以字符串的形式读入,然后遍历一遍求每位数字的最小值即可。

二、B. No Casino in the Mountains

#include 
using namespace std;

typedef long long ll;
typedef pair pii;

void solve()
{
    int n,k;
    cin>>n>>k;
    vectora(n);
    for(int i=0;i>a[i];
    }

    int cnt=0;
    for(int i=0,j=0;i>t;
    while(t--)
    {
        solve();    
    }
    return 0;
}

这个题就是纯模拟,没啥好说的。

代码就是双指针往后滑,每次先让指针i来到数值是0的位置,即好天气,然后让指针j往后滑完这个全0区间。借着每次检查一下这个区间长度是否够了k,够了就统计答案即可。

三、C. I Will Definitely Make It

#include 
using namespace std;

typedef long long ll;
typedef pair pii;

void solve()
{
    int n,ini;
    cin>>n>>ini;
    vectora(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    int pos=a[ini];
    
    sort(a.begin()+1,a.end());

    int start=0;//当前下标
    for(int i=1;i<=n;i++)
    {
        if(a[i]==pos)
        {
            pos++;
            start=i;
            break;
        }
    }

    int level=1;
    for(int i=start;i>t;
    while(t--)
    {
        solve();    
    }
    return 0;
}

这个题其实也是纯模拟。

观察可以发现,其实原来的顺序并没有意义,那么可以考虑先对塔从小到大排序,这样就可以从低一步一步往高处跳了。之后的思路就是纯模拟,观察可以发现,即使可以一步跳得更高,那么同样可以一步一步按顺序跳。那就是每次算出往上跳一个的蓄力时间和死亡时间,只有死亡时间比蓄力时间长才能活。

因为需要排序,所以得先用pos记一下初始的高度,然后排序,再遍历一遍找到排序后所在的下标,后面只需要从这开始即可。之后设置变量level表示水位,然后计算还有多久死亡die和蓄力时间time。所以死亡时间的公式就是当前高度减去水位高度加一,蓄力时间就是下一步的高度减当前高度。如果一旦发现死亡时间小于等于蓄力时间,那么直接输出no,否则就更新当前位置pos和水位level即可。

四、D. This Is the Last Time

#include 
using namespace std;

typedef long long ll;
typedef pair pii;

void solve()
{
    ll n,k;
    cin>>n>>k;
    vector>a(n+1,vector(3));
    for(int i=1;i<=n;i++)
    {
        cin>>a[i][0]>>a[i][1]>>a[i][2];
    }

    sort(a.begin()+1,a.end(),[&]
    (const vector&x,const vector&y){
        return x[0]>t;
    while(t--)
    {
        solve();    
    }
    return 0;
}

这个题其实也是贪心。

思路就是先对赌场按进入限制从小到大排序,接着每次先把能进的所有赌场抓出来。因为这里的钱数是直接更改后的钱数,所以对于一个有最大的钱数的赌场,即使还有很多比它小的钱数的赌场,不管去不去,去几个比最大钱数小的赌场,最后都需要去这个最大钱数的赌场把钱更新为这个最大值。所以只需要去一次这个最大钱数的赌场即可,那就是每次统计进去赌完了的钱数的最大值。之后,假如这个最大值能把当前的钱数更新得更大,那么就可以去赌场赚钱,然后去后续解锁的赌场玩。如果发现最大值都不比当前钱大,那当前就是能达到的最大钱数,后续就不玩了直接返回。

五、E. G-C-D, Unlucky!

#include 
using namespace std;

typedef long long ll;
typedef pair pii;

ll gcd(ll a,ll b)
{
    return b==0?a:gcd(b,a%b);
}

ll lcm(ll a,ll b)
{
    return a/gcd(a,b)*b;
}

void solve()
{
    int n;
    cin>>n;
    vectorp(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>p[i];
    }
    vectors(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>s[i];
    }

    //易得p[i]=gcd(p[i-1],a[i]) s[i]=gcd(s[i+1],a[i])
    //所以p[i]可以整除a[i],s[i]可以整除a[i] -> p[i]|a[i],s[i]|a[i] -> a[i]里包含p[i]和s[i]的质因子幂
    //所以lcm(p[i],s[i])可以整除a[i]
    //假设存在一个质数k,使得a[i]也能被k*lcm整除 -> a[i]里会有lcm里没有的质因子幂
    //所以k不能被p[i-1]和s[i+1]整除 -> p[i-1]和s[i+1]不包含额外的质因子
    //所以直接可以在a[i]里去掉质因子k,使a[i]直接为lcm(p[i],s[i])
    
    //所以只需要先让a[i]为lcm(p[i],s[i]),再check前后缀是否合法即可

    vectora(n+1);
    for(int i=1;i<=n;i++)
    {
        a[i]=lcm(p[i],s[i]);
    }

    ll x=a[1];
    for(int i=1;i<=n;i++)
    {
        x=gcd(x,a[i]);
        if(x!=p[i])
        {
            cout<<"NO"<=1;i--)
    {
        x=gcd(x,a[i]);
        if(x!=s[i])
        {
            cout<<"NO"<>t;
    while(t--)
    {
        solve();    
    }
    return 0;
}

赛时拼尽全力没想到lcm……

赛时其实想到了p[i]等于gcd(p[i-1],a[i])和s[i]等于gcd(s[i+1],a[i])这个结论,所以根据唯一分解定理,抽象一点理解,p[i]和s[i]一定是a[i]分解后的一部分。那么可以得到p[i]和s[i]可以整除a[i],所以一定有lcm(p[i],s[i])可以整除a[i]。那么就存在一个质数k,使得a[i]也可以被k*lcm(p[i],s[i])整除,因为a[i]里一定有p[i]和s[i]中没有的质因子幂。所以,k就不能被p[i-1]和s[i+1]整除,因为这俩里不包含k这个额外的质因子。所以在构造a[i]时,可以直接令a[i]等于lcm(p[i],s[i])即可。

所以之后的思路就是先构造数组a,让a[i]就等于lcm。然后再分别遍历数组p和数组s,检验这个a[i]是否满足p[i]和s[i]的要求即可。

六、F. 1-1-1, Free Tree!

#include 
using namespace std;

typedef long long ll;
typedef pair pii;
typedef pairpll;

const int MAXN=2e5+5;

//不能每次遍历所有边 -> 菊花图
//考虑对每个节点建立一张map
//存到其孩子节点的每种颜色的代价和
//每次考察更改节点的父亲和自己的map即可

void dfs(int u,int father,
vector>&g,vector&color,vector&fa,vector&sums,vector>&sumColor,ll &ans,vector&wf)
{
    fa[u]=father;

    for(int i=0;i>n>>q;

    vector>g(n+1);//邻接表
    vectorcolor(n+1);//颜色
    vectorfa(n+1);//每个节点的父节点
    vectorsums(n+1);//每个节点到孩子节点的代价和
    vector>sumColor(n+1);//每个节点孩子节点每种颜色的代价和
    vectorwf(n+1);//到父节点的代价
    ll ans=0;//总代价

    for(int i=1;i<=n;i++)
    {
        cin>>color[i];
    }
    for(int i=0;i>u>>v>>w;
        g[u].push_back({v,w});
        g[v].push_back({u,w});
    }

    //初始化
    dfs(1,0,g,color,fa,sums,sumColor,ans,wf);

    for(int i=0,u,c;i>u>>c;

        //改颜色
        if(c!=color[u])
        {
            //把更改前的代价都删掉

            //有父节点且初始颜色和父节点不同
            if(fa[u]!=0&&color[fa[u]]!=color[u])
            {
                ans-=wf[u];
            }
            
            //初始不同色的代价和
            ll diff=sums[u];
            
            //当前节点的所有孩子里有和当前节点初始颜色相同的
            if(sumColor[u].find(color[u])!=sumColor[u].end())
            {
                diff-=sumColor[u][color[u]];
            }
            ans-=diff;

            //更改颜色

            //有父节点
            if(fa[u]!=0)
            {
                //减掉父节点到当前节点初始颜色的代价
                sumColor[fa[u]][color[u]]-=wf[u];
            }
            
            //改颜色

            color[u]=c;
            if(fa[u]!=0)
            {
                //修改颜色更改后父节点的map
                sumColor[fa[u]][color[u]]+=wf[u];
            }

            //把更改后新增的代价加回来

            if(fa[u]!=0&&color[fa[u]]!=color[u])
            {
                ans+=wf[u];
            }

            //加上所有的代价
            ans+=sums[u];
            if(sumColor[u].find(color[u])!=sumColor[u].end())
            {
                //减去颜色相同的代价
                ans-=sumColor[u][color[u]];
            }
        }

        cout<>t;
    while(t--)
    {
        solve();    
    }
    return 0;
}

这思路太逆天了……

多少得学一下lambda表达式了,多样例测试开全局变量就得每次清空,不全局写函数又得全传进去,关键这个题把query单独写成一个函数还会TLE…… 

首先,上来第一反应肯定是每次考察修改的点的每条边。但再思考可以发现,假如这是一个菊花图,那么每次都考察所有边的时间复杂度太高了,肯定过不去。那么就需要设计一种结构,使得每次修改的时间是O(1)的。

先考虑对每一个节点建立一张map,存其所有孩子节点中,每种颜色对应的代价之和。那么思路就是当修改节点u时,去只需要考察节点u的父亲节点和所有孩子节点即可。那么除了这张map,还需要建立fa数组存每个节点的父节点,wf数组存每个节点去其父节点的代价,以及sums数组存每个节点到其所有孩子节点的代价之和。有了这些信息,那么对于每个要修改的节点u,可以先扣掉其原本的代价,然后修改颜色,再加上修改后的代价。这样就实现了每次修改的时间是O(1)的。

所以就是上来先dfs一遍初始化所有信息。如果和父节点不同色,那么需要把代价加到ans里,同时构建fa数组,wf数组,sums数组和sumColor这张map。

之后处理每个query,如果修改了颜色,那么需要先把更新前的代价都扣掉。首先,如果和父节点的颜色不同,需要先从ans里扣掉到父节点的代价。之后处理孩子节点,设置diff表示颜色不同的代价和。那么diff就是所有孩子节点的代价和sums减去颜色相同的代价和sumColor,最后让ans减去diff即可。如果有父节点的话,需要再扣掉父节点的sumColor中当前节点颜色的代价。

之后修改颜色,更新color数组,然后更新父节点的sumColor中修改后颜色的代价。

最后把修改后的代价加回来,那就是如果修改后的颜色和父节点不同,就先往ans里加上去父节点的代价。然后,首先让ans加上去所有孩子节点的代价和,再减去和修改后颜色相同的代价和即可。最后每次都输出ans即可。

总结

数论和图论永远的痛……

END

你可能感兴趣的:(算法,c++)