Codeforces Round 889 (Div. 2)
C2. Dual (Hard Version)
题解
可以按照如下步骤实现:
1.得到一个足够大的正数或负数,这里的足够大指的是让它们的绝对值成为最大。
假设得到足够大的正数需要经过操作数为\(x_1\),得到足够大的负数需要经过操作数为\(x_2\),不难想到其中一个的值为0,对于另外一个,通过不断叠加自身,可以将步骤数控制在5步以内。因此可以得到\(x_1+x_2 \leq5\)。
2.将足够大的数加到剩下的数中,使整体保持符号相同。
假设得到整体正数所需要的操作数为\(y_1\),得到整体负数所需要的操作数为\(y_2\),显然\(y_1+y_2\leq20\)
3.可以得到\(x_1 + x_2 + y_1 + y_2 \leq 25\),因此我们选择\(x_1 + y_1\) 和 \(x_2 + y_2\)中的最小值,该最小值一定\(\leq12\)
4.对于正数来说,我们只要从前往后扫描数组,对每一个数加上前一个数即可保证单调不减,同理,对负数来说只需要从后往前加上后缀和即可。
code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define pb push_back
#define ff first
#define se second
#define pii pair<ll,ll>
const int maxn = 1e5+5;
void solve(){
int n;
cin >> n;
vector<int>a(n+1);
vector<pii>ans1, ans2;
int sum1 = 0, sum2 = 0;
int x1 = 0, x2 = 0, y1 = 0, y2 = 0;
int cnt1 = 0, cnt2 = 0;
int idx = 0, idy = 0;
int maxx = -25, minn = 25;
for(int i = 1; i <= n; ++i)
cin >> a[i];
for(int i = 1; i <= n; ++i){
if(a[i] < minn){
idy = i;
minn = a[i];
}
if(a[i] > maxx){
idx = i;
maxx = a[i];
}
if(a[i] < 0)
y1++;
if(a[i] > 0)
y2++;
}
if(minn + maxx < 0){
if(maxx > 0){
while(minn + maxx < 0){
ans1.pb({idx,idx});
x1++;
maxx += maxx;
}
cnt1 += x1;
for(int i = 1; i <= n; ++i){
if(a[i] < 0){
ans1.pb({i,idx});
}else if(a[i] > 0){
ans2.pb({i,idy});
}
}
cnt1 += y1;
cnt1 += n-1;
cnt2 += (y2 + n-1);
if(cnt1 < cnt2){
cout << cnt1 << '\n';
for(auto it : ans1){
cout << it.ff << ' ' << it.se << '\n';
}
for(int i = 2; i <= n; ++i)
cout << i << ' ' << i-1 << '\n';
}else{
cout << cnt2 << '\n';
for(auto it : ans2){
cout << it.ff << ' ' << it.se << '\n';
}
for(int i = n-1; i >= 1; --i){
cout << i << ' ' << i + 1 << '\n';
}
}
}else{
cout << n-1 << '\n';
for(int i = n-1; i >= 1; --i){
cout << i << ' ' << i+1 << '\n';
}
}
}else{
if(minn < 0){
while(minn + maxx > 0){
ans2.pb({idy,idy});
x2++;
minn += minn;
}
cnt2 += x2;
for(int i = 1; i <= n; ++i){
if(a[i] > 0){
ans2.pb({i,idy});
}else if(a[i] < 0){
ans1.pb({i,idx});
}
}
cnt2 += y2;
cnt2 += n-1;
cnt1 += (y1 + n-1);
if(cnt2 < cnt1){
cout << cnt2 << '\n';
for(auto it : ans2){
cout << it.ff << ' ' << it.se << '\n';
}
for(int i = n-1; i >= 1; --i)
cout << i << ' ' << i+1 << '\n';
}else{
cout << cnt1 << '\n';
for(auto it : ans1){
cout << it.ff << ' ' << it.se << '\n';
}
for(int i = 2; i <= n; ++i){
cout << i << ' ' << i - 1 << '\n';
}
}
}else{
cout << n-1 << '\n';
for(int i = 2; i <= n; ++i){
cout << i << ' ' << i-1 << '\n';
}
}
}
}
int main(){
int t;
cin >> t;
while(t--){
solve();
}
return 0;
}
D. Earn or Unlock
题解
\(dp[i]\)表示以第i个物品结尾时所能得到的最大分数
只需要考虑此时第i个物品是否已经解锁即可。
我们可以用二进制来保存每一只种状态。因此可以考虑用bitset来进行优化。
code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define pb push_back
#define ff first
#define se second
#define pii pair<ll,ll>
const int maxn = 1e5+5;
int a[maxn];
ll s[2*maxn];
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
for(int i = 1; i <= n; ++i){
cin >> a[i];
s[i] = s[i-1] + a[i];
}
bitset<2*maxn>dp;
dp.set(a[1]);
dp.reset(0);
ll ans = a[1];
for(int i = 2; i <= n; ++i){
if(dp.test(i-1)){
ans = max(ans, s[i] - (i-1));
}
dp |= dp << a[i];
dp.reset(i-1);
}
for(int i = n + 1; i <= 2 * n; ++i){
if(dp.test(i-1)){
ans = max(ans, s[n] - (i-1));
}
}
cout << ans;
return 0;
}
E. Expected Destruction
题解
要使集合为空,则需要保证相邻两数两两合并。
定义\(dp[i][j]\)为将\(i,j(i<j)\)合并为一个数的期望操作次数,则有状态转移方程
当\(i = j\)时,\(dp[i][j] = 0\)。当\(j=m+1\)时,\(dp[i][j] = m-i+1\)
答案即为\(\sum dp_{a_i,a_{i+1}}\)
code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define pb push_back
#define ff first
#define se second
#define pii pair<ll,ll>
const int maxn = 1e5+5;
const ll mod = 1000000007;
ll a[505];
ll dp[505][505];
ll quickpow(ll a, ll n){
ll res = 1;
while(n > 0){
if(n & 1)
res = (res * a) % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
ll sum = 0;
cin >> n >> m;
ll inv2 = quickpow(2, mod-2);
for(int i = 1; i <= n; ++i){
cin >> a[i];
}
a[n+1] = m + 1;
for(int i = 1;i <= m; ++i)
dp[i][m+1] = m+1-i;
for(int i = m; i>=1; --i){
for(int j = m; j > i; --j){
dp[i][j] = inv2*((dp[i+1][j]+1)%mod+dp[i][j+1]%mod) % mod;
}
}
for(int i = 1; i <= n; ++i){
sum = (sum + dp[a[i]][a[i+1]]) % mod;
}
cout << sum;
return 0;
}
这道题还可以从反面来考虑,对于每一个数来说,在互不相遇的情况下,共需要消耗的操作数为\(\sum(m+1-a_i)\),所以可以定义\(dp[i][j]\)为值为\(i\)和值为\(j\)的数相遇时到移出集合还需要的期望操作数,答案即为\(\sum(m+1-a_i)-\sum{dp_{a_{i},a_{i+1}}}\)
code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define pb push_back
#define ff first
#define se second
#define pii pair<ll,ll>
const int maxn = 1e5+5;
const ll mod = 1000000007;
int m, n;
int a[505];
int vis[505][505],f[505][505];
ll inv2;
ll quickpow(ll a, ll n){
ll res = 1;
while(n > 0){
if(n & 1)
res = (res * a) % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
ll dp(int x, int y){
if(x == y){
return m+1-x;
}
if(y == m + 1)
return 0;
if(vis[x][y])
return f[x][y];
vis[x][y] = 1;
f[x][y] = (inv2 * dp(x+1,y) + inv2*dp(x,y+1)) % mod;
return f[x][y];
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
inv2 = quickpow(2, mod-2);
ll ans = 0;
for(int i = 1; i <= n; ++i)
cin >> a[i];
for(int i = 1; i <= n; ++i){
ans = (ans + m+1-a[i]) % mod;
}
for(int i = 1; i < n; ++i){
ans = (ans - dp(a[i],a[i+1]) + mod) % mod;
}
cout << ans;
return 0;
}

浙公网安备 33010602011771号