Codeforces Round 1035(A~D)
A. Add or XOR
给出两个数a,b,我们可以进行若干次以下的操作:
1.花费x,得到\(a=a+1\) ;
2.花费y,得到\(a=a\oplus 1\)
求把a变成b的最小代价,若不能报告-1;
解析:
1.若a>b,那么看看能不能通过一次异或,即是否能使\(a=a-1\)从而等于b,如果可以输出y,反之-1
2.若a<b,检查异或是否能使\(a=a+1\) ,如果可以就选择花费小的使其加一,若不行就直接加一
范围很小直接累加就行
void solve() {
int a, b, x, y;
cin >> a >> b >> x >> y;
if (a > b) {
if ((a ^ 1) == b) {
cout << y << '\n';
}else{
cout <<-1<<'\n';
}
} else {
int ans = 0;
while (a != b) {
if (a & 1) {
a++;
ans += x;
} else {
a++;
ans += min(x, y);
}
}
cout <<ans<<'\n';
}
}
B. Line Segments
给出平面上的两个点p,q的横纵坐标,以及一个长度为n的数组{\(a_i\)},问是否走n步每步长度为\(a_i\),能否从p走到q
很容易想到能走的最长距离是\(\sum{a_i}\) ,现在考虑最短距离,即考虑能否走回原点,最直接的走法就是最长的边向前,其他边都用于往回走,即判断 \(\sum{a_i}-a_{max}\)是否大于\(a_{max}\),如果是一定可以走回原点,如果不是那只能走到 \(\sum{a_i}-2*a_{max}\) ,最后判断|pq|在不在这个范围内即可
void solve() {
int n, px, py, qx, qy;
cin >> n >> px >> py >> qx >> qy;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
sort(all(a));
int mx = 0;
int mn = 0;
for (int i = 0; i < n - 1; i++) {
mx += a[i];
mn += a[i];
}
mx += a[n - 1];
mn -= a[n - 1];
if (mn >= 0) mn = 0;
else mn = abs(mn);
mx = mx * mx;
mn = mn * mn;
int dis = (px - qx) * (px - qx) + (py - qy) * (py - qy);
if (mn <= dis && dis <= mx) {
cout << "YES\n";
} else {
cout << "NO\n";
}
}
C. A Good Problem
给定n,l,r,k 构造一个数组{\(a_i\)},满足\(l \le a_i \le r\) ,且各个数字按位异或和等于各个数字按位与和,且在所有合法方案里{\(a_i\)}数组字典序最小,输出\(a_k\)
解析:
1.n为奇数直接构造\(l,l,l,l\)即可
2.n为\(2\)不存在合法数组
3.n为大于2的偶数:先构造\(n-2\)个\(l\),此时按位与为\(l\),按位异或为0,在构造\(2\)个一样的数字b,此时按位异或和为0,让按位与为0即可,那么只要b的每一位都与\(l\)与后结果为0即可,所以b的最高位一定比\(l\)大一位,那么剩下的数组全填0最优(不管什么数字和0与都为0),判断这个b在不在范围内即可,最后输出第k个\(a_i\)
注意若使用\((1<<i)\) 请使用\((1LL<<i)\)
void solve() {
int n, l, r, k;
cin >> n >> l >> r >> k;
if (n & 1) {
cout << l << '\n';
} else if (n == 2) {
cout << -1 << '\n';
} else {
int mxl = -1;
for (int i = 0; i <= 60; i++) {
if (l & (1ll << i)) mxl = i;
}
// cout << mxl<<' ';
int ck = (1ll << (mxl + 1));
if (ck <= r) {
if (k == n || k == n - 1) cout << ck << '\n';
else cout << l << '\n';
} else {
cout << -1 << '\n';
}
}
}
D.Token Removing
有一个数组{\(a_i\)},这个数组上的每一个数字满足\(0\leqslant a_i \leqslant i\) ,依次进行\(n\)次操作,第\(i\)次操作若\(a_i \ne 0\) ,则删除[\(a_i,i\)]之间的一个标记,求所有合法数组{\(a_i\)}删除标记的方案数,答案对m取模。
解析:注意这里需要的到的是方案数而不是数组 (本蒟蒻在这里绕了半天) 。由于每一次操作只会影响[\(a_i,i\)]不会影响\(i\)之后的部分,所以我们考虑从后往前dp这样后面的数字不会影响到已经形成的方案。我们定义\(dp[i][j]\)为 \([i,n]\)中有\(j\)个标记被删除的方案数,递推公式如下:
1.\(i\)处标记未被删除,\(dp[i][j]=dp[i+1][j]\) (那么没有增加新的标记,方案数和上一次一样)
2.\(i\)处标记被删除,\(dp[i][j]=dp[i+1][j-1]*i*(n-i+1-j)\) (增加了一个新的标记,为了增加这个新的标记可以在那些未被删除标记的位置即共\((n-i+1-j)\)个位置填上\([1-i]\)那么就可以将\(i\)处的标记删除即原方案{a,b,c,d}变为{i,a,b,c,d},方案为{i,a,b,c,d}的有\(i*(n-i+1-j)\)种)
void solve() {
int n, mod;
cin >> n >> mod;
vector<vector<int>> dp(n + 2, vector<int>(n + 2, 0));
dp[n][0] = 1;
for (int i = n; i >= 1; i--) {
for (int j = 0; j <= n - i; j++) {
dp[i - 1][j] = (dp[i - 1][j] + dp[i][j]) % mod;
dp[i - 1][j + 1] = (dp[i - 1][j + 1] + dp[i][j] * i % mod * (n - i - j + 1) % mod) % mod;
}
}
int ans = 0;
for (int i = 0; i <= n; i++) {
ans=(ans+dp[0][i])%mod;
}
cout << ans <<'\n';
}