Codeforces Round 982 (Div. 2)(前四题)

a题

翻译:

        您正在给一个无限方形网格着色,网格中的所有单元格最初都是白色的。为此,我们会给你 n 个图章。每个图章都是宽 w_{i}、高 h_{i}的矩形。

        每个图章只能使用一次,在网格上给与图章大小相同的矩形涂上黑色。您不能旋转印章,对于每个单元格,印章必须完全覆盖或完全不覆盖。您可以在网格上的任何位置使用图章,即使图章区域覆盖的部分或全部单元格已经是黑色的。

        使用所有图章后,黑色方格相连区域的最小周长之和是多少?

思路:

       要想周长小每个图章的重叠面积就得尽量大,而周长由图章的最长高和最长底决定,即周长=2*(最长高+最长底)。

实现:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<ll,ll>;
const int N = 4e5+100;
ll t,n,w,h;
void solve(){
    cin>>n;
    ll ww = 0,hh = 0;
    for (int i=0;i<n;i++){
        cin>>w>>h;
        ww = max(ww,w);
        hh = max(hh,h);
    }
    cout<<2*(ww+hh)<<endl;
}
int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>t;
    while (t--) solve();
}

b题

 翻译:

        斯大林排序(Stalin Sort)是一种幽默的排序算法,旨在删除不合适的元素,而不是费心地对它们进行正确排序,其时间复杂度为 O(n)。

算法如下:从数组中的第二个元素开始,如果它严格小于前一个元素(忽略那些已经被删除的元素),那么就删除它。继续迭代数组,直到按非递减顺序排序。例如,数组[1,4,2,3,6,5,5,7,7]在斯大林排序后会变成[1,4,6,7,7]。

        如果对数组中的任意子数组重复执行斯大林排序,并能按非递增顺序排序,那么我们就将该数组定义为脆弱数组。

        给定一个由 n 个整数组成的数组 a,求从数组中删除的整数的最小个数,以使数组变得脆弱

        如果数组 a 可以从数组 b 中删除开头的几个(可能是零或全部)元素和结尾的几个(可能是零或全部)元素,那么数组 a 就是数组 b 的子数组。

思路:

       照他这个删法要变成非递增顺序排序,只有变成一条直线(即所有剩余值相同),要实现只要保持左端点的右侧没有比该点大的数即可。

        遍历每个点i当左端点用,比较花费大小,花费=i-1+右侧比i大的数。

实现:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<ll,ll>;
const int N = 4e5+100;
ll t,n;
void solve(){
    cin>>n;
    vector<ll> a(n+10);
    for (int i=0;i<n;i++) cin>>a[i];
    ll ans = LLONG_MAX;
    for (int i=0;i<n;i++){
        ll temp = i;
        for (int j=i+1;j<n;j++){
            temp += a[i]<a[j];
        }
        ans = min(temp,ans);
    }
    cout<<ans<<endl;
}
int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>t;
    while (t--) solve();
}

c题

 翻译:

给你一个最初包含 n 个整数的数组 a。在一次操作中,你必须完成以下操作:

  • 选择一个位置 i,使得1<i \leq \left| a\right|a_{i}=\left| a\right|+1-i 其中 \left| a \right| 是数组的当前大小。
  • 在 a 的末尾添加 i-1 个零。

尽可能多次地执行此操作后,数组 a 的最大可能长度是多少?

思路:

        a_{i}=|a|+1-i \iff a_{i}+(i-1)=|a|也就是一个数加上其下标可以实现数组的增长,而添加0必定不能使数组再增长。递归所有f(new_len)=f(len)+a[len](a为字典键值对为len和所有和为len的i)

实现:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<ll,ll>;
ll t,n,ans;
map<ll,vector<ll>> a;
set<ll> vis;
void dfs(ll len){
    if (vis.find(len)!=vis.end()) return;
    vis.insert(len);
    ans = max(ans,len);
    for (auto i=a[len].begin();i!=a[len].end();i++){
        dfs(len+(*i));
    }
}
void solve(){
    cin>>n;
    ans = n;a.clear();vis.clear();
    for (ll i=0,num;i<n;i++) cin>>num,a[i+num].push_back(i);
    dfs(n);
    cout<<ans<<endl;
}
int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>t;
    while (t--) solve();
}

D1题

 翻译:

        给你一个长度为 n 的数组 a 和一个长度为 m 的数组 b( b_{i}>b_{i+1} 对于1\leq i< m) 。最初,k 的值是 1。你的目标是通过重复执行这两种操作中的一种,使数组 a 为空:

  • 类型1 -- 如果 k 的值小于m,且数组 a 不为空,则可以将 k 的值增加 1。这不会产生任何费用。
  • 类型2 -- 删除数组 a 的一个非空前缀,使其总和不超过 b_{k}。这会产生 m-k 的代价。

        你需要最小化操作的花费,使数组 a 为空。如果不可能通过任何操作序列做到这一点,则输出-1。否则,输出操作的最小总花费。

思路:

他分两个操作  1.a没删光时k+1;2.移除a的前缀和不大于b_{k}的部分。

定义f[i][j]为目前k为j时,a数组删到i处所需要的花费。

操作1:f[i][j+1]=f[i][j]

操作2:f[(当前位置使用b_{k}能到的最远处)][j] = f[i][j]+(m-k)

为什么不管i与最远处之间的位置?

        因为要让结果变好,每次删必定删越多越好,关心之间的没有意义

如何快速得出当前位置使用b_{k}能到的最远处?

        使用前缀和+二分搜索,找到从i开始前缀和小于等于b_{k}的位置。

        或使用滑动窗口,由于k不变i可一直向后推。

下面的dp还可以优化为一维可以试试(微笑)。

实现:

二分实现:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<ll,ll>;
const ll N = 3e5+100;
const ll MAXN = 1e17;
ll t,n,m;
vector<ll> a(N),b(N);
void solve(){
    cin>>n>>m;
    for (int i=1;i<=n;i++) cin>>a[i],a[i]+=a[i-1];
    for (int i=1;i<=m;i++) cin>>b[i];
    vector<vector<ll>> dp(n+1,vector<ll>(m+1,MAXN));
    for (int i=0;i<=m;i++) dp[0][i] = 0;
    for (int i=1;i<=n;i++){
        for (int j=1;j<=m;j++){
            dp[i][j] = min(dp[i][j],dp[i][j-1]);
            int r = upper_bound(a.begin()+1,a.begin()+n+1,a[i-1]+b[j])-a.begin()-1;
            dp[r][j] = min(dp[r][j],dp[i-1][j]+m-j);
        }
    }
    if (dp[n][m]==MAXN) dp[n][m]=-1;
    cout<<dp[n][m]<<endl;
}
int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>t;
    while (t--) solve();
}

 滑动窗口:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<ll,ll>;
const ll N = 3e5+100;
const ll MAXN = 1e17;
ll t,n,m;
vector<ll> a(N),b(N);
void solve(){
    cin>>n>>m;
    for (int i=1;i<=n;i++) cin>>a[i],a[i]+=a[i-1];
    for (int i=1;i<=m;i++) cin>>b[i];
    vector<vector<ll>> dp(n+1,vector<ll>(m+1,MAXN));
    for (int i=0;i<=m;i++) dp[0][i] = 0;
    for (int i=1;i<=m;i++){
        for (int j=1,l=0;j<=n;j++){
            while (a[j]-a[l]>b[i]) l++;
            dp[j][i] = min(dp[j][i],dp[j][i-1]);
            dp[j][i] = min(dp[j][i],dp[l][i]+m-i);
        }
    }
    if (dp[n][m]==MAXN) dp[n][m]=-1;
    cout<<dp[n][m]<<endl;
}
int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>t;
    while (t--) solve();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cando-01

随心而动,随性而为。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值