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

A

暴力即可

#include <iostream>
#include <algorithm>
#include <unordered_map>

#define SIS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)

using namespace std;

const int N = 1e3+10;

int a[N],n,t;

int _abs(int x){
    return x>=0?x:-x;
}
void change(int i){
    if(i==0) return;
    if(i&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]);
        }
    }
}
bool jg(){
    for(int i=1;i<=n;++i){
        if(a[i] != i) return false;
    }
    return true;
}
int main()
{
    SIS;
    cin >>t;
    while(t--){
        cin >> n;
        for(int i=1;i<=n;++i) cin >> a[i];
        int start = 0;
        bool flag;
        while(1){
            change(start);
            flag = jg();
            if(flag){
                cout << start << '\n';
                break;
            }
            ++start;
        }
    }
    return 0;
}

B

分奇数和偶数讨论,可以发现奇数比偶数多一种情况;

对于选手来说,如果花费了一个点数去打break,那么他自己也必定会被break一次;

因此 一 个 点 数 = 两 次 b r e a k 一个点数=两次break =break

#include <iostream>
#include <algorithm>
#include <set>
#include <vector>

#define SIS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)

using namespace std;

typedef long long ll;

const int N = 1e3+10;

ll a,b,t;
set<ll> s;

ll _abs(ll x){
    return x>=0?x:-x;
}
void solve(){
    if(min(a,b) == 0){
        ll mx = max(a,b);
        if(mx&1){
            cout << 2 << '\n';
            cout << (mx/2) << ' ' << (mx/2+1) << '\n';
        }else{
            cout << 1 << '\n';
            cout << (mx/2) << '\n';
        }
        return;
    }
    ll sum = a+b;
    ll minn = min(a,b);
    //奇数和偶数都有sum/2个 minn对应字母
    ll tmp = sum/2 - minn;
    if(minn <= sum/2){
        s.insert(tmp);
    }
    ll tmin = minn;
    while(tmin--){
        s.insert(tmp+2);
        tmp+=2;
    }
    if(sum&1){
        //奇数额外多了这种情况
        //有sum/2+1个minn对应字母
        tmp = sum/2+1 - minn;
        if(minn <= sum/2+1){
            s.insert(tmp);
        }
        tmin = minn;
        while(tmin--){
            s.insert(tmp+2);
            tmp+=2;
        }
    }
    cout << s.size() << '\n';
    for(auto z : s){
        cout << z << ' ';
    }
    cout << '\n';
}
int main()
{
    SIS;
    cin >> t;
    while(t--){
        s.clear();
        cin >> a >> b;
        solve();
    }
    return 0;
}

C

计算出打通每个洞穴所需要的最少体力;

二分答案即可

#include <iostream>
#include <algorithm>
#include <set>
#include <vector>

#define SIS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)

using namespace std;

typedef long long ll;

const int N = 1e5+10;

ll a,b,t,n;
vector<ll> v[N];
struct Node{
    ll v,idx;
}mx[N];

bool cmp(Node p,Node q){
    return p.v < q.v;
}
bool jg(ll hp){
    for(int i=1;i<=n;++i){
        if(hp>=mx[i].v){
            hp+=v[mx[i].idx].size();
        }else{
            return false;
        }
    }
    return true;
}
int main()
{
    SIS;
    cin >> t;
    while(t--){
        cin >> n;
        for(ll i=0;i<=n+1;++i){
            mx[i].v = mx[i].idx = 0;
            v[i].clear();
        }
        for(ll i=1,k;i<=n;++i){
            cin >> k;
            mx[i].idx = i;
            for(ll j=1,u;j<=k;++j){
                cin >> u;
                v[i].push_back(u);
                ll len = v[i].size()-1;
                mx[i].v = max(mx[i].v,u-len+1);
            }
        }
        sort(mx+1,mx+1+n,cmp);
        ll L = -1,R = mx[n].v;//(L,R]
        while(L+1<R){
           ll mid = (L+R) >> 1;
           if(jg(mid)) R = mid;
           else L = mid;
        }
        cout << R << '\n';
    }
    return 0;
}

D1

首先根据题目的意思,打出暴力的代码;

这份代码的 f [ i ] 表 示 从 n 到 i 的 方 案 数 f[i]表示从n到i的方案数 f[i]ni

#include <iostream>
#include <unordered_map>

using namespace std;

const int N = 2e5+10;

typedef long long ll;

int n,m;

int f[N];

int main(){
	cin >> n >> m;
	f[n] = 1;
	for(int i=n;i>=2;--i){
		for(int j=1;j<=i-1;++j){
			f[i-j] = (f[i-j]+f[i]) % m;
		}
		for(int j=2;j<=i;++j){
			f[i/j] = (f[i/j]+f[i]) % m;
		}
	}
	cout << f[1] << '\n';
	return 0;
}

这份代码会TLE,那我们考虑如何优化它;


因为是计数,那么我从n记录到1,和从1反向记录到n,他们的结果是相同的;

那么我们先改变一下代码,如下;

这份代码的 f [ i ] 表 示 从 1 到 i 的 方 案 数 f[i]表示从1到i的方案数 f[i]1i

#include <iostream>
#include <unordered_map>

using namespace std;

typedef long long ll;

const int N = 2e5+10;

int n,m;

int f[N];

int main(){
	cin >> n >> m;
	f[1] = 1;
	for(int i=2;i<=n;++i){
		for(int j=1;j<=i-1;++j){
			f[i] = (f[i-j]+f[i]) % m;
		}
		for(int j=2;j<=i;++j){
			f[i] = (f[i/j]+f[i]) % m;
		}
	}
	cout << f[n] << '\n';
	return 0;
}


我们可以发现,对于每个i来说,我们都要计算 1 到 i − 1 的 和 1到i-1的和 1i1,那么我们可以用前缀和来维护,这样就优化掉一个循环了,如下;

#include <iostream>
#include <unordered_map>

using namespace std;

typedef long long ll;

const int N = 2e5+10;

int n,m;

int f[N];

int main(){
	cin >> n >> m;
	f[1] = 1;
	int sum = 1;
	for(int i=2;i<=n;++i){
		f[i] = (sum%m+f[i]%m) % m;
		for(int j=2;j<=i;++j){
			f[i] = (f[i/j]+f[i]) % m;
		}
		sum=(sum%m+f[i]%m)%m;
	}
	cout << f[n] << '\n';
	return 0;
}

然而还是会T,那么我们要继续优化;

这里需要引入一个知识整除分块,具体可以参考这篇博客整除分块

这里我写一下我的思路;

首先打表看一下
在这里插入图片描述
可以发现,表中同样的值会连续出现,成块状分布,所以从 1 到 n 1到n 1n的数组表可根据数值划分为不同的分块,且分块数远远小于 n;

如果我们是枚举统计每个值,那么是 O ( n ) O(n) O(n)的,而按照分块来统计,则是 O ( n ) O(\sqrt{n}) O(n );


我们想一次统计一个块的话,那么就需要知道这个块的左右坐标,记为 L 和 R L和R LR;

令 k = ⌊ n i ⌋ 令k=\lfloor \frac{n}{i} \rfloor k=in
最小的 i 就 是 L , 最 大 的 i 就 是 R i就是L,最大的i就是R iLiR
L L L我们只需要枚举(见代码就清晰了),R需要计算;

显然有 k ∗ i ≤ n k*i≤n kin,我们想让 i 尽 可 能 的 大 , 最 好 能 满 足 k ∗ i = n i尽可能的大,最好能满足k*i=n iki=n;

但是往往取不到,因此我们只需要让 i = ⌊ n k ⌋ i=\lfloor \frac{n}{k} \rfloor i=kn即可取到最大的 i i i

k = ⌊ n i ⌋ k=\lfloor \frac{n}{i} \rfloor k=in代入 i m a x = ⌊ n k ⌋ i_{max}=\lfloor \frac{n}{k} \rfloor imax=kn,可以得到 i m a x = ⌊ n ⌊ n i ⌋ ⌋ i_{max}=\lfloor \frac{n}{\lfloor \frac{n}{i} \rfloor} \rfloor imax=inn


这样我们就可以将

for(int j=2;j<=i;++j){
		f[i] = (f[i/j]+f[i]) % m;
	}

优化成

for(int L=2,R;L<=i;L=R+1){
        R = i/(i/L);
        f[i] = (f[i]+(R-L+1)*f[i/L])%m;
	}

完整Code

#include <iostream>
#include <unordered_map>

using namespace std;

typedef long long ll;

const int N = 2e5+10;

int n,m;

ll f[N];

int main(){
	cin >> n >> m;
	f[1] = 1;
	int sum = 1;
	for(int i=2;i<=n;++i){
		f[i] = sum%m;
		for(int L=2,R;L<=i;L=R+1){
            R = i/(i/L);
            f[i] = (f[i]+(R-L+1)*f[i/L])%m;
		}
		sum=(sum%m+f[i]%m)%m;
	}
	cout << f[n] << '\n';
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值