Codeforces Round 982 (Div. 2)(A~D1)

对dp还不是特别熟练
只做到了C(还是太菜了),开始前刚好各种事情来了,vp晚了10多分钟开才始做题,喜提排名(不是)3000+,后面有时间就尽量把dp补掉
比赛链接:https://codeforces.com/contest/2027/problem

A. Rectangle Arrangement

你需要在一个无限的方格网格上涂色,所有格子最初都是白色的。为了完成这项任务,你有 n 个印章。每个印章是一个宽度为 wi、高度为 hi 的矩形。
你将使用 每个 印章 恰好一次 在网格上涂一个与印章同样大小的矩形区域为黑色。你不能旋转印章,对于每个格子,印章必须完全覆盖它或者完全不覆盖它。你可以在网格上的任何位置使用印章,即使印章覆盖的一些或全部格子已经是黑色的。
在使用完所有印章之后,你能得到的黑色方格区域的 周长之和 的最小值是多少?
Input
每个测试包含多个测试案例。第一行包含测试案例的数量 t(1≤t≤500)。每个测试案例的描述紧随其后。
每个测试案例的第一行包含一个整数 n(1≤n≤100)。
接下来的 n 行中,第 i 行包含两个整数 wi 和 hi(1≤wi,hi≤100),分别代表第 i 个印章的宽度和高度。
Output
对于每个测试案例,输出一个整数——在使用完所有印章后,你能得到的黑色方格区域周长之和的最小值。
Sample
5
5
1 5
2 4
3 3
4 2
5 1
3
2 2
1 1
1 2
1
3 2
3
100 100
100 100
100 100
4
1 4
2 3
1 5
3 2


20
8
10
400
16

思路:这题我是老老实实进行了区域覆盖,然后dfs了一遍区域,看周长
没想到是小学数学,2max(h)max(w),也确实是啊,vp时真是脑子抽了

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll                                     long long
#define lowbit(x) (x & -x)
//#define endl "\n"//                           交互题记得删除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
ll ksm(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
{
ans = ans % mod * (x % mod) % mod;
}
x = x % mod * (x % mod) % mod;
y >>= 1;
}
return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
struct s
{
    ll x,y;
}p[2500];
ll a[400][400];
ll d[4]={0,1,-1,0};
ll e[4]={1,0,0,-1};
int main()
{
    fio();
    ll t;
    cin>>t;
    while(t--)
    {
        for(ll i=1;i<=100;i++)
        {
            for(ll j=1;j<=100;j++)
            a[i][j]=0;
        }
        ll n;
        cin>>n;
        for(ll i=1;i<=n;i++)
        {
            ll w,h;
            cin>>w>>h;
            for(ll k=1;k<=w;k++)
            {
                for(ll u=1;u<=h;u++)
                {
                    a[k][u]=1;
                }
            }
        }
        ll ans=0;
        for(ll i=0;i<=101;i++)
        {
            for(ll j=0;j<=101;j++)
            {
             for(ll u=0;u<=3;u++)
             {
                ll nx=i+d[u];
                ll ny=j+e[u];
                if(nx>=1&&nx<=100&&ny>=1&&ny<=100&&a[i][j]==0)
                {
                    if(a[nx][ny])ans++;
                }
             }   
            }
        }
        cout<<ans<<endl;
    }
}

B. Stalin Sort

斯大林排序(Stalin Sort)是一种幽默的排序算法,它不是为了正确地对元素进行排序,而是为了消除那些不在适当位置的元素,从而实现 O(n) 的时间复杂度。
斯大林排序的步骤如下:从数组的第二个元素开始,如果它严格小于前一个元素(忽略那些已经被删除的元素),那么就删除它。继续遍历数组,直到数组按照非递减顺序排序。例如,数组 [1,4,2,3,6,5,5,7,7] 在经过斯大林排序后变为 [1,4,6,7,7]
我们定义一个数组为“易受攻击”的,如果你可以通过反复对它的任意子数组应用斯大林排序,无论需要多少次,来将其排序成非递增顺序。
给定一个包含 n 个整数的数组 a,确定必须从数组中移除的最小整数数量,以使其变得“易受攻击”。
如果数组 a 可以通过从数组 b 删除若干(可能为零或全部)开头和结尾的元素来获得,那么数组 a 就是数组 b 的一个子数组。
Input
每个测试包含多个测试案例。第一行包含一个整数 t1t500)——测试案例的数量。之后是测试案例的描述。
每个测试案例的第一行包含一个整数 n1n2000)——数组的大小。
每个测试案例的第二行包含 n 个整数 a1,a2,,an1ai109)。
保证所有测试案例中 n 的总和不超过 2000
Output
对于每个测试案例,输出一个整数——必须从数组中移除的最小整数数量,以使其变得“易受攻击”。
Sample
6
7
3 6 4 9 2 5 2
5
5 4 4 2 2
8
2 2 4 4 6 6 10 10
1
1000
9
6 8 9 10 12 9 7 5 4
7
300000000 600000000 400000000 900000000 200000000 400000000 200000000


2
0
6
0
4
2
样例解析:
在第一个测试案例中,最优解是移除数字 39。这样我们剩下的数组为 a=[6,4,2,5,2]。为了证明这个数组是“易受攻击”的,我们可以先对子数组 [4,2,5] 应用斯大林排序,得到 a=[6,4,5,2],然后对子数组 [6,4,5] 应用斯大林排序,得到 a=[6,2],这是一个非递增序列。
在第二个测试案例中,数组已经是非递增的,所以我们不需要移除任何整数。
思路:说了这么多,其实题意等价于问第一个是不是最大的数,枚举每一个数作为第一个,前面的要全删除,然后后面比这个数大的也要删除,这样子就可以保证排序到最后可以获得不递增的排列
不妨象想想,第一个数非最大数,且后面有更大数时是无解的

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll                                     long long
#define lowbit(x) (x & -x)
//#define endl "\n"//                           交互题记得删除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
ll ksm(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
{
ans = ans % mod * (x % mod) % mod;
}
x = x % mod * (x % mod) % mod;
y >>= 1;
}
return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
ll a[4500];
int main()
{
    fio();
    ll t;
    cin>>t;
    while(t--)
    {
        ll n;
        cin>>n;
        for(ll i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        ll ans=9999999999;
        for(ll i=1;i<=n;i++)
        {
            ll cnt=i-1;
          for(ll j=i+1;j<=n;j++)
          {
            if(a[j]==a[i])continue;
            else if(a[j]>a[i])cnt++;
          }
          ans=min(ans,cnt);
        }
        cout<<ans<<endl;
    }
}

C. Add Zeros

您将获得一个最初包含 n 个整数的数组 a。在一个操作中,您必须执行以下操作:
选择一个位置 i,使得 1<i|a|ai=|a|+1i,其中 |a| 是数组的当前大小。
a 的末尾追加 i1 个零。
在您想要多次执行此操作后,数组 a 的最大可能长度是多少?
Input
每个测试包含多个测试用例。第一行包含测试用例的数量 t1t1000)。测试用例的描述紧随其后。
每个测试用例的第一行包含 n1n3105)——数组 a 的长度。
每个测试用例的第二行包含 n 个整数 a1,a2,,an1ai1012)。
保证所有测试用例的 n 之和不超过 3105
Output
对于每个测试用例,输出一个整数 —— 在执行一系列操作后,数组 a 的最大可能长度。
Sample
4
5
2 4 6 2 5
5
5 4 4 5 1
4
6 8 2 3
1
1


10
11
10
1
样例解析:
在第一个测试用例中,我们可以先选择 i=4,因为 a4=5+14=2。在此之后,数组变为 [2,4,6,2,5,0,0,0]。然后我们可以选择 i=3,因为 a3=8+13=6。在此之后,数组变为 [2,4,6,2,5,0,0,0,0,0],其长度为 10。可以证明,没有任何操作序列会使最终数组更长。
在第二个测试用例中,我们可以选择 i=2,然后 i=3,然后 ii=4。最终数组将是 [5,4,4,5,1,0,0,0,0,0,0],长度为 11。
思路:其实由这个式子ai=|a|+1i可得到aii=|a|+1,当一个数可以进行操作时,其必然符合这个式子,不妨先求出aii|a|1作为特征值然后反过来用特征值去记录符合的下标,然后每次增加i-1时,可以去看看是否有符合的特征值存在,如果存在,则进行搜索,不在就返回。因为答案显然是没有一个固定策略可选,所以选择记忆化递归(dp的一种形式)然后从以0作为特征值去搜即可算出答案,对于每个阶段的增值可能最好用map记住,然后从其他地方搜过来时,时间复杂度就大大减小了

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll                                     long long
#define lowbit(x) (x & -x)
//#define endl "\n"//                           交互题记得删除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
ll ksm(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
{
ans = ans % mod * (x % mod) % mod;
}
x = x % mod * (x % mod) % mod;
y >>= 1;
}
return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
ll n;
map<ll,set<ll>>q;
map<ll,ll>op;
ll dfs(ll cnt,ll z)//记忆化?
{
    if(op[cnt])
    {
        return op[cnt]+z;
    }
    ll ans=z;
  for(auto j:q[cnt])
  {
    ans=max(ans,dfs(cnt+j-1,z+j-1));
  }
   op[cnt]=ans-z;
   return ans;
}
ll a[450000];
int main()
{
    fio();
    ll t;
    cin>>t;
    while(t--)
    {
       op.clear();
       cin>>n;
       for(ll i=1;i<=n;i++)
       {
        cin>>a[i];
        if(a[i]>=(n-i+1)&&i!=1)
        {
            q[a[i]-(n-i+1)].insert(i);
        }
       }
       cout<<dfs(0,n)<<endl;
       q.clear();
    }
}

D1.The Endspeaker (Easy Version)

这是这个问题的简单版本。唯一的区别是在这个版本中,你只需要输出操作的最小总成本。你必须解决两个版本才能进行hack。
你给定了一个长度为 n 的数组 a,和一个长度为 m 的数组 b(对于所有 1i<mbi>bi+1)。最初,k 的值是 1。你的目标是通过反复执行以下两种操作之一来使数组 a 变空:
类型 1 — 如果 k 的值小于 m 并且数组 a非空 的,你可以将 k 的值增加 1。这不会产生任何成本。
类型 2 — 你移除数组 a 的一个非空前缀,使得其和不超过 bk。这会产生一个成本,为 mk
你需要最小化使数组 a 变空的操作总成本。如果通过任何操作序列都无法实现这一点,输出 1。否则,输出操作的最小总成本。
Input
每个测试包含多个测试用例。第一行包含测试用例的数量 t1t1000)。测试用例的描述紧随其后。
每个测试用例的第一行包含两个整数 nm1n,m31051nm3105)。
每个测试用例的第二行包含 n 个整数 a1,a2,,an1ai109)。
每个测试用例的第三行包含 m 个整数 b1,b2,,bm1bi109)。
同时保证对于所有 1i<m,有 bi>bi+1
保证所有测试用例中 nm 的总和不超过 3105
Output
对于每个测试用例,如果有可能使数组 a 变空,则输出操作的最小总成本。
如果没有任何操作序列可以使数组 a 变空,则输出一个整数 1
Sample
5
4 2
9 3 4 3
11 7
1 2
20
19 18
10 2
2 5 2 1 10 3 2 9 9 6
17 9
10 11
2 2 2 2 2 2 2 2 2 2
20 18 16 14 12 10 8 6 4 2 1
1 6
10
32 16 8 4 2 1


1
-1
2
10
4
样例解析:
在第一个测试用例中,一个产生总成本为 1 的最优操作序列如下:
执行类型 2 的操作。选择前缀为 [9]。这会产生一个成本为 1
执行类型 1 的操作。现在 k 的值是 2。这不会产生任何成本。
执行类型 2 的操作。选择前缀为 [3,4]。这会产生一个成本为 0
执行类型 2 的操作。选择前缀为 [3]。这会产生一个成本为 0
数组 a 现在为空,所有操作的总成本为 1
在第二个测试用例中,由于 a1>b1,无法移除数组的任何前缀,因此无法通过任何操作序列使数组 a 变空。

思路:二维dp+二分前缀优化,设dp[j][i],表示到第一数组第j个数且第二个数组第i个时的最小费用,然后每次对于第i个数时利用二分+前缀去找符合的区间
这里给出优化掉第二个二维数组的方法,设二分的位置位r
则有dp[j][i]=min(dp[j][i],dp[r][i]+mi,dp[r][i1]+mi,dp[j][i1])
然后每次走完时得进行一次循环
dp[j][i]=min(dp[j][i],dp[j][i1]),意思就是选择k++或者k不变,这样子可以把最后答案传递到dp[n][m]

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll                                     long long
#define lowbit(x) (x & -x)
//#define endl "\n"//                           交互题记得删除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
ll ksm(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
{
ans = ans % mod * (x % mod) % mod;
}
x = x % mod * (x % mod) % mod;
y >>= 1;
}
return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
ll a[450000];
ll b[450000];
ll c[450000];
ll pre[450000];
int main()
{
    fio();
    ll t;
    cin>>t;
    while(t--)
    {
        ll n,m;
        cin>>n>>m;
        ll dp[n+5][m+5];
        ll cnt=0;
        memset(dp,0x3f3f3f3f3f3f,sizeof dp);
        for(ll i=1;i<=n;i++)cin>>a[i],cnt=max(cnt,a[i]),pre[i]=pre[i-1]+a[i];
        for(ll j=1;j<=m;j++)cin>>b[j],c[j]=0,dp[0][j]=0;
        if(b[1]<cnt)
        {
            cout<<-1<<endl;
        }
        else 
        {
            for(ll i=1;i<=m;i++)
            {
                ll cnt=0;
                for(ll j=1;j<=n;j++)
                {
                    if(a[j]>b[i])cnt++;
                }
                c[i]=cnt;
            }
            for(ll i=1;i<=m;i++)
            {
                ll pd=0;
                ll cnt=0;
                ll sum=0;
                for(ll j=1;j<=n;j++)
                {
                    if(cnt==c[i])pd=1;
                    if(pd==0)
                    {
                        if(a[j]>b[i])cnt++;
                    }
                    else 
                    {   
                        ll l=0,r=j;
                        while(l<r)
                        {
                            ll mid=(l+r)>>1;
                            if(pre[j]-pre[mid]<=b[i])
                            {
                                r=mid;
                            }
                            else 
                            l=mid+1;
                        }
                        dp[j][i]=min({dp[j][i],dp[r][i]+m-i,dp[j][i-1],dp[r][i-1]+m-i}); 
                    }
                }
                for(ll j=1;j<=n;j++)
                {
                    dp[j][i]=min(dp[j][i-1],dp[j][i]);
                }
            }
            cout<<(dp[n][m]==0x3f3f3f3f3f3f?-1:dp[n][m])<<endl;
        }
    }
}
posted @   长皆  阅读(68)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示