Codeforces Round #740 (Div. 2, based on VK Cup 2021 - Final (Engine))(A-D2)

Codeforces Round #740 (Div. 2, based on VK Cup 2021 - Final (Engine))

A - Simply Strange Sort(模拟)

简单模拟,没什么好说的。

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 1e5 ,mod=1e9 + 7;

void solve()
{
    int n;
    cin>>n;
    int a[n+10];
    for(int i=1 ;i<=n;i++)cin>>a[i];
    int cnt = 0;

    while(!(is_sorted(a+1,1+a+n)))
    {
        cnt ++;
        if(cnt&1)
            for(int i=1;i<=n-2;i+=2)
                if(a[i]>a[i+1])
                    swap(a[i],a[i+1]);
        else
            for(int i=2;i<=n-1;i+=2)
                if(a[i]>a[i+1])
                    swap(a[i],a[i+1]);
    }


    cout << cnt <<endl;
}   
signed main()
{
    ios::sync_with_stdio();

    int T;cin>>T;
    int T1=clock();
    while(T--)
        solve();

    int T2=clock();
    cerr<<endl<<" Time : "<< T2-T1 <<"ms."<<endl; 
    return 0;
}

B - Charmed by the Game(模拟)

两个人轮流发球,给出二人胜利场数,求破发球方的胜利场次的所有情况。谁第一个发球都有可能

我们并不需要某一场具体谁赢了,破发球场必定为偶数场(或奇数场),那么就把场次分为奇数场和偶数场,遍历所有可能情况即可。

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
//#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 10 + 1e5, mod = 1e9 + 7;
int a, b, n, odd, eve;

bool check(int x)
{
    if (0 <= x && x <= odd)
        if (0 <= a - x && a - x <= eve)
                    return 1;
    return 0;
}

void solve()
{
    cin >> a >> b;
    n = a + b;
    odd = n / 2 + n % 2, eve = n / 2;
    int cnt = 0;
    set<int> res;
    for (int i = 0; i <= a; i++)
    {
        if (check(i))
        {
            res.insert(odd+a-2*i);
            res.insert(eve-a+2*i);
        }
    }
    cout<<res.size()<<endl;
    for(auto v:res)cout<<v<<' ';
    cout<<endl;
}
signed main()
{
    ios::sync_with_stdio();

    int T;
    cin >> T;
    int T1 = clock();
    while (T--)
        solve();

    int T2 = clock();
    cerr << endl
         << " Time : " << T2 - T1 << "ms." << endl;
    return 0;
}

C - Deep Down Below(思维+贪心+二分)

有n个山洞 ,里面有m只怪,每只怪护甲为k,现让人进去杀怪,只有人的武力值大于怪物护甲值时,才可以击败,人进入一个山洞后只能按顺序把怪物全杀完才能出来,每杀一只怪,人能力值加一,进入洞的顺序不定,求能杀完所有怪的最小能力值,

进入n个洞的最小能力值不好找到,但可以较简单的找到进入一个山洞所需的最小能力值,因此我们可以先预处理出一个st_pow 记录每个山洞的初始能力值,显然应优先选择初始能力值小的先进入,所以排序。之后在最小初始能力值到最大初始能力值的区间上二分即可。

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 1e5 ,mod=1e9 + 7;
int n,m;
struct P
{
    int pow;
    int len;    
};

P st_pow[N];

bool cmp(P a,P b)
{
    if(a.pow!=b.pow)
        return a.pow<b.pow;
    return a.len>b.len;
}

bool check(int x)// 模拟进山洞杀怪 若失败返回0,成功返回1
{
    int tmp = x;
    for(int i =1 ;i<=n;i++)
    {
        if(tmp>=st_pow[i].pow)
            tmp+=st_pow[i].len;
        else return 0;
    }
    return 1;
}

void solve()
{
    cin >> n;
    for(int i = 0;i<n;i++)
    {
        cin >> m;
        st_pow[i+1].len = m;
        // 得到初始能力值
        int tmp = 0;
        for(int j=1;j<=m;j++)
        {
            int a;
            cin>>a;
            if(tmp + j -1 <=a)
                tmp +=(a-tmp-j+1) + 1 ; 
        }
        st_pow[i+1].pow = tmp;
    }
    sort(st_pow + 1, st_pow + 1 + n, cmp);
    int l = st_pow[1].pow, r = st_pow[n].pow;
    while(l<=r)
    {
        int mid = l + r >>1 ;
        if(check(mid))
            r = mid - 1;
        else l = mid + 1;
    }
    cout << l << endl;
}
signed main()
{
    ios::sync_with_stdio();

    int T;cin>>T;
    int T1=clock();
    while(T--)
        solve();

    int T2=clock();
    cerr<<endl<<" Time : "<< T2-T1 <<"ms."<<endl; 
    return 0;
}

更新一个较简单的二分写法,取自二分查找为什么总是写错?

int l = st_pow[1].pow - 1, r = st_pow[n].pow + 1;
    while(l + 1 != r)
    {
        int mid = l + r >>1 ;
        if(check(mid))
            r = mid ;
        else l = mid ;
    }
    cout << r << endl;// r就是所有能通过情况的区域,也就是“红色区域”

D - Up the Strip

有n个格子,问从n到1 的方案数。
走法有两种 ,
1.从x 到x-y y属于[1,x-1]
2.从x到x/z下取整 , z属于 [2,x]

很明显的dp问题 定义 dp[i] 表示从i到1的所有方案数,对于第一种走法,显然是先走到 j 再到1 , 枚举所有 j 就是 一个 dp的前缀和。
对于第二种走法,暴力扫一遍,可以得到朴素写法。

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define int long long
#define ll long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 2e5;
int n , mod;
int dp[N];
int sum[N];
bool vis[N];
void solve()
{
    cin >> n >> mod;
    sum[1]=1,dp[1] = 1;
    for(int i = 2 ;i<=n;i++)  
    {
        dp[i] = sum[i - 1];// case 1
       
        // case 2
        for(int j=2;j<=i;j++)dp[i]+= dp[i/j]%mod,dp[i]%=mod; //   O(n)

        sum[i] = (sum[i-1]+dp[i])%mod;
    }
    cout <<dp[n] << endl;
}
signed main()
{
    ios::sync_with_stdio();
        solve();
    
    return 0;
} 

复杂度O(n^2) 不出所料炸了,对于第一种情况 复杂度已经到O(1) 了,显然要优化第二种情况:

 for(int j=2;j<=i;j++)dp[i]+= dp[i/j]%mod,dp[i]%=mod; //   O(n)

因为状态是由i/j 转移过来的 ,对于整除一定会出现连续相同值,可以采用分块优化。(不清楚的话可以打表验证)

 for(int l = 2 ,r; l <= i ; l = r + 1)
       {
           r=i/(i/l);// 得到连续值的最右端,因为r / i  == l / i 是成立的(余数为零和余数最大的情况)
           dp[i] +=(dp[i/l] * (r-l+1)) % mod;
       }

从而,我们将时间复杂的优化为了O(n*sqrt(n) )

ps:n/i 的不同取值最多有sqrt(n)

综上,我们得到了第一版ac代码

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define int long long
#define ll long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 2e5;
int n , mod;
int dp[N];
int sum[N];
bool vis[N];
void solve()
{
    cin >> n >> mod;
    sum[1]=1,dp[1] = 1;
    for(int i = 2 ;i<=n;i++)  
    {
        dp[i] = sum[i - 1];// case 1
       
        // case 2
        //for(int j=2;j<=i;j++)dp[i]+= dp[i/j]%mod,dp[i]%=mod;  O(n)

        // 分块优化 O(sqrt(n))
        for(int l = 2 ,r; l <= i ; l = r + 1)
        {
            r=i/(i/l);
            dp[i] +=(dp[i/l] * (r-l+1)) % mod;
        }
        sum[i] = (sum[i-1]+dp[i])%mod;
    }
    cout <<dp[n] << endl;
}
signed main()
{
    ios::sync_with_stdio();
        solve();
    
    return 0;
}

这足够我们将D的简单版过掉,但对于原版4e6的数据量还是不行。


我们继续从状态转移中寻找答案。

对于一个multiset S(x) 记录从x到1的所有状态,那么对于S(x + 1) ,它相较于S(x):
对于case 1: 多了一个x(令y = 1, x + 1 --> x+1-1 ==x)。
对于case 2 :多了一个 1 (令z = x+1 x + 1 --> x+1/x+1 == 1)。

所以可以得到一个更简洁的表达:
dp[i+1] += dp[i] + dp[i] +dp[1]
但是,如果对case2:因为 分子从x -> x + 1 ,所以有部分状态发生了改变。
打表可以发现:

2 :1
3 :1 1
4 :2 1 1
5 :2 1 1 1
6 :3 2 1 1 1
7 :3 2 1 1 1 1
8 :4 2 2 1 1 1 1
9 :4 3 2 1 1 1 1 1
10 :5 3 2 2 1 1 1 1 1
11 :5 3 2 2 1 1 1 1 1 1
12 :6 4 3 2 2 1 1 1 1 1 1
13 :6 4 3 2 2 1 1 1 1 1 1 1
14 :7 4 3 2 2 2 1 1 1 1 1 1 1
15 :7 5 3 3 2 2 1 1 1 1 1 1 1 1
16 :8 5 4 3 2 2 2 1 1 1 1 1 1 1 1
17 :8 5 4 3 2 2 2 1 1 1 1 1 1 1 1 1
18 :9 6 4 3 3 2 2 2 1 1 1 1 1 1 1 1 1
19 :9 6 4 3 3 2 2 2 1 1 1 1 1 1 1 1 1 1
20 :10 6 5 4 3 2 2 2 2 1 1 1 1 1 1 1 1 1 1
21 :10 7 5 4 3 3 2 2 2 1 1 1 1 1 1 1 1 1 1 1
22 :11 7 5 4 3 3 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1
23 :11 7 5 4 3 3 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1
24 :12 8 6 4 4 3 3 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1
25 :12 8 6 5 4 3 3 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1
26 :13 8 6 5 4 3 3 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1
27 :13 9 6 5 4 3 3 3 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1
28 :14 9 7 5 4 4 3 3 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1
29 :14 9 7 5 4 4 3 3 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
30 :15 10 7 6 5 4 3 3 3 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

对case2的状态转移,除了多了z = x + 1 的一个1外,x+1的因数的状态比x的状态多加了1 , 设i为x+1的因子 ,则在dp[x+1]上应再加一个dp[i]-dp[i-1]
即 dp[x+1]+=dp[i]-dp[i-1]


综上

dp[i] += dp[i-1]*2+dp[1]

dp[i] +=dp[k]-dp[k-1] (k为i的约数)

但是,得到所有因数储存的话会被卡空间,所以选择更加简单的一种等价写法。

ll d = dp[i] - dp[i-1];
        for(int j = 2; j<=n/i;j++)
            dp[i*j] += d;

我们只需要再每次求出dp[i]后 ,枚举所有i的倍数提前加到dp[i*j]上,就可以得到相同的效果。
加上所有因数造成的“影响”==对因数倍数的预处理
这也是欧拉筛的思想

最后的最后,我们终于得到ac代码

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 4e6;
ll dp[N];
int n,mod;
void solve()
{
    cin >> n >> mod;
    dp[1] = 1, dp[2] = 2;
    for(int i = 2;i<=n;i++)// 细节1 : 2要特判掉
    {
        if(i>2) dp[i] = (dp[i] + dp[i-1] * 2 + 1)% mod;
        ll d = dp[i] - dp[i-1];
        for(int j = 2; j<=n/i;j++)
            dp[i*j] += d;
    }
    cout << (dp[n]%mod+mod)%mod << endl;// 细节2 要取最小正整数值,有数据卡longlong 溢出是必然的
}
signed main()
{
    ios::sync_with_stdio();

    solve();

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值