Codeforces 1068 Div2(ABCD)

前言

孩子们,明天新生赛,会赢吗qwq

一、A. Sleeping Through Classes

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

#define dbg(x) cout<<#x<<" "<<x<<endl
#define vdbg(a) cout<<#a<<endl; for(auto x:a) cout<<x<<" ";cout<<endl
#define INF 1e9
#define INFLL 1e18
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;

void solve()
{
    int n,k;
    cin>>n>>k;
    string s;
    cin>>s;
    s=" "+s;

    int i=1;
    while(i<=n&&s[i]=='0')
    {
        i++;
    }

    int ans=i-1;
    while(i<=n)
    {
        int l=k;
        while(i<=n&&l>0)
        {
            if(s[i]=='1')
            {
                l=k;
            }
            else
            {
                l--;
            }
            i++;
        }

        int j=i;
        while(j<=n&&s[j]=='0')
        {
            j++;
        }

        ans+=j-i;

        i=j;
    }
    cout<<ans<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

上来这个A就不简单,想了一会才过的......

题意倒是很明确,直接模拟就行,就是碰到必须要上的课上完刷新清醒时间,否则的话就可以统计答案了。

二、B. Niko's Tactical Cards

妈的,我感觉这个B难爆了,好不容易才想出来,到底为什么会有那么多人过B......

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

#define dbg(x) cout<<#x<<" "<<x<<endl
#define vdbg(a) cout<<#a<<endl; for(auto x:a) cout<<x<<" ";cout<<endl
#define INF 1e9
#define INFLL 1e18
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;

void solve()
{
    int n;
    cin>>n;
    vector<ll>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    vector<ll>b(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>b[i];
    }

    ll cmx=0;
    ll cmn=0;
        
    for(int i=1;i<=n;i++)
    {
        ll mx=max({cmx-a[i],b[i]-cmn,b[i]-cmx,cmn-a[i]});
        ll mn=min({cmx-a[i],b[i]-cmn,b[i]-cmx,cmn-a[i]});

        cmx=mx;
        cmn=mn;
    }
    cout<<cmx<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

这个题真的难想,只能说B题放dp太搞了......

因为操作1可以保持当前的分数不变,而操作2可以让当前的分数取相反数。那么操作1肯定是希望当前的分数越大越好,而操作2肯定是希望让当前分数越小越好。那么操作1肯定是在之前能达到的最大值的基础上操作,操作2肯定是在之前能达到的最小值的基础上操作。所以可以考虑分别维护之前能达到的最大值和最小值,然后看这两个值排列组合能否更新最大最小值即可。

三、C. Kanade's Perfect Multiples

吃一堑吃一堑吃一堑了......

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

#define dbg(x) cout<<#x<<" "<<x<<endl
#define vdbg(a) cout<<#a<<endl; for(auto x:a) cout<<x<<" ";cout<<endl
#define INF 1e9
#define INFLL 1e18
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;

//赛时傻逼代码,但能过
void solve1()
{
    ll n,k;
    cin>>n>>k;
    vector<ll>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    //一定要排序!!!!!
    sort(a.begin()+1,a.end());

    map<ll,vector<int>>mp;
    for(int i=1;i<=n;i++)
    {
        mp[a[i]].push_back(i);
    }

    vector<int>vis(n+1);

    vector<ll>p;
    for(auto [x,vec]:mp)
    {
        int first=vec[0];

        if(!vis[first])
        {
            p.push_back(x);

            for(auto i:vec)
            {
                vis[i]=1;
            }

            for(ll c=2;c<=n-first+3&&x*c<=k;c++)
            {
                auto iter=mp.find(x*c);
                if(iter!=mp.end())
                {
                    for(auto i:iter->second)
                    {
                        vis[i]=1;
                    }
                }
                else
                {
                    cout<<-1<<endl;
                    return ;
                }
            }
        }
    }

    cout<<p.size()<<endl;
    for(auto x:p)
    {
        cout<<x<<" ";
    }
    cout<<endl;
}

//正解
void solve2()
{
    ll n,k;
    cin>>n>>k;
    vector<ll>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    map<ll,vector<int>>mp;
    for(int i=1;i<=n;i++)
    {
        mp[a[i]].push_back(i);
    }

    vector<int>vis(n+1);

    vector<ll>p;
    for(auto [x,vec]:mp)
    {
        int first=vec[0];

        if(!vis[first])
        {
            p.push_back(x);

            for(auto i:vec)
            {
                vis[i]=1;
            }

            for(ll j=2*x;j<=k;j+=x)
            {
                auto iter=mp.find(j);
                if(iter!=mp.end())
                {
                    for(auto i:iter->second)
                    {
                        vis[i]=1;
                    }
                }
                else
                {
                    cout<<-1<<endl;
                    return ;
                }
            }
        }
    }

    cout<<p.size()<<endl;
    for(auto x:p)
    {
        cout<<x<<" ";
    }
    cout<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve2();    
    }
    return 0;
}

其实赛时已经考虑到了势能分析,但由于是记录每个数的下标是否访问过,且往后枚举的步长和当前所在的位置有关,所以必须进行排序,而赛时恰好就是一直没想到需要排序......

整体思路就是对于原数组中出现的数,肯定是选择最小的因数放入b数组。举个例子,对于[2,3,4,6,8]这个数组,肯定是把2放进b数组,这样后面的[4,6,8]就都被覆盖了。

先说说赛时的傻逼思路,在排完序后,用map维护每个数出现的位置。之后从小到大枚举每个出现的数,若当前数没在之前的过程中被标记,那么就说明这个数必须要放入b数组,那么就标记这个数所有出现的位置,然后去枚举这个数的倍数。在枚举这个数的倍数的过程中,在保证不超过k的前提下,若当前数出现在i位置,那么只需要枚举到n-i+3倍,确保有没出现的会被抓出来即可。因为后面的倍数要么超过k,要么因为数组长度必然不会出现。

正解的势能分析就是,对于每个选择的数,直接暴力枚举其倍数。因为数组的元素个数是有限的,所以这个数再大也不会大到哪去。即使存在一个非常大的数,由于是从小到大枚举倍数,所以在中间必然会找到一个没出现的倍数,就直接return了,所以可以直接暴力枚举。

四、D. Taiga's Carry Chains

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<" "<<x<<endl
#define vdbg(a) cout<<#a<<endl; for(auto x:a) cout<<x<<" ";cout<<endl
#define INF 1e9
#define INFLL 1e18
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;

const int MAXB=64;
const int MAXK=32;

int dp[MAXB+1][MAXK+1][2];

ll n,k;

void clear()
{
    for(int i=0;i<=MAXB;i++)
    {
        for(int j=0;j<=k;j++)
        {
            dp[i][j][0]=dp[i][j][1]=INF;
        }
    }
}

void solve()
{
    cin>>n>>k;

    //当k>=32时,在每一步都使得n产生一个进位后,n会变成2的幂
    //之后每次增加都会产生一个进位,所以答案就是popcount(n)+k-1

    int pc=__builtin_popcount(n);

    if(n==0)
    {
        cout<<max(0ll,k-1)<<endl;
        return ;
    }
    if(k>=32)
    {
        cout<<pc+k-1<<endl;
        return ;
    }

    //观察到,最终的答案为popcount(n)+k-popcount(end),end为最终的结果
    //所以为了最大化这个值,需要最小化popcount(end)
    //考虑定义dp[i][j][c]为在处理了i个最小有效位后,用了j次操作
    //c=0表示没有来自i-1的进位,c=1表示有进位,此时的最小值
    //所以对于第i位,要么选择这位操作要么不操作

    clear();

    dp[0][0][0]=0;

    for(int i=0;i<MAXB;i++)
    {
        int cur=(n>>i)&1;

        for(int j=0;j<=k;j++)
        {
            for(int c=0;c<=1;c++)
            {
                if(dp[i][j][c]==INF)
                {
                    continue;
                }

                //当前位加上之前的总和
                int sum=cur+c;
                //当前位的状态
                int bit=sum&1;
                //对下一位的进位
                int nc=sum>>1;

                dp[i+1][j][nc]=min(dp[i+1][j][nc],dp[i][j][c]+bit);

                if(j+1<=k)
                {
                    //操作后当前位加一
                    sum++;
                    bit=sum&1;
                    nc=sum>>1;

                    dp[i+1][j+1][nc]=min(dp[i+1][j+1][nc],dp[i][j][c]+bit);
                }
            }
        }
    }

    int ans=INF;
    for(int j=0;j<=k;j++)
    {
        for(int c=0;c<=1;c++)
        {
            if(dp[MAXB][j][c]==INF)
            {
                continue;
            }

            ans=min(ans,dp[MAXB][j][c]+c);
        }
    }

    cout<<pc+k-ans<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

逆天dp思路,补题的时候全往贪心上想了,压根没想到应该dp......

首先上来这个就很难想到,就是当k大于等于32时,因为n是2的30次方,所以此时肯定把原来位置的1全进位完了,剩下的就是2的某次方的形式,那么往后每一次操作都可以产生一个进位。那么多举几个例子就能发现,最终的答案为popcount(n)+k-1。推广一下有,当k小于32时,最终产生的进位数为popcount(n)+k-popcount(end),这里end是最终的数字。那么为了最大化这个值,可以考虑最小化popcount(end)。

之后就考虑定义dp[i][j][c]为在处理了i个有效位后,用了j次操作,c=0表示没有来自低位的进位,c=1表示有来自低位的进位,此时的最小值。所以对于第i位,可以选择操作或者不操作。所以每次计算出当前位加上之前的进位的总和,当前位的状态以及对下一位的进位。那么不操作就是直接加上当前位的状态,操作就是让总和加1,再加上此时当前位的状态。最后,枚举所有可能的状态,求出最小值,注意要考虑进位。

总结

还得练,最近几周感觉多训构造挺有效的,思路明显清晰了很多。

END

相关推荐
少许极端2 小时前
算法奇妙屋(十八)-子数组系列(动态规划)
算法·动态规划·子数组
阿沁QWQ3 小时前
C++的map和set
开发语言·c++
地平线开发者3 小时前
征程 6P/H 计算平台部署指南
算法·自动驾驶
Xの哲學3 小时前
Linux二层转发: 从数据包到网络之桥的深度解剖
linux·服务器·算法·架构·边缘计算
我也要当昏君4 小时前
计算机组成原理
算法
Fiona-Dong4 小时前
Louvain 算法
python·算法
维构lbs智能定位4 小时前
蓝牙信标、UWB等主流室内定位无线技术的参数对比、核心算法和选型指南详解(二)
算法·蓝牙信标·uwb·主流室内定位无线技术
charlie1145141914 小时前
现代C++工程实践:简单的IniParser3——改进我们的split
开发语言·c++·笔记·学习
fish_xk4 小时前
c++的引用和类的初见
开发语言·c++