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;
}