Codeforces Round #693 (Div. 3)A~G解题报告
A Cards for Friends
原题信息
http://codeforces.com/contest/1472/problem/A
解题思路
本题就是一个找 x/2i=old,y/2j=oldx/2^i=old,y/2^j=oldx/2i=old,y/2j=old, 返回 2i∗2j>=n2^i*2^j>=n2i∗2j>=n
一般这样的题目都需要注意使用 LL
AC代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <fstream>
using namespace std;typedef long long LL;bool inline Check(LL x, LL y, LL n)
{LL ret = 1;while ((x & 1) == 0){ret <<= 1;x >>= 1;if (ret >= n)return true;}while ((y & 1) == 0){y >>= 1;ret <<= 1;if (ret >= n)return true;}if (ret >= n) return true;else return false;
}int main()
{int T; cin >> T;while (T -- ){static LL x, y, n;cin >> x >> y >> n;if (Check(x, y, n))cout << "YES\n";elsecout << "NO\n";}return 0;
}
B Fair Division
原题信息
http://codeforces.com/contest/1472/problem/B
解题思路
能否将糖果平均分,直接分类讨论
首先统计 1gram1gram1gram 数量为cnt1cnt1cnt1,2gram2gram2gram数量为cnt2cnt2cnt2下面对cnt1,cnt2cnt1,cnt2cnt1,cnt2进行分类讨论
cnt1偶数,cnt2偶数cnt1偶数,cnt2偶数cnt1偶数,cnt2偶数
————可以直接进行平均分
cnt1偶数,cnt2奇数cnt1偶数,cnt2奇数cnt1偶数,cnt2奇数
————当cnt1=0cnt1=0cnt1=0时,不可以平均分
————当cnt1!=0cnt1!=0cnt1!=0时,可以平均分,只需要将cnt1−=2,cnt2++cnt1-=2,cnt2++cnt1−=2,cnt2++即可
cnt1奇数,cnt2偶数或偶数cnt1奇数,cnt2偶数或偶数cnt1奇数,cnt2偶数或偶数
————因为cnt1cnt1cnt1为奇数,导致总的grams根本就是个奇数,是不可能平均分的。
AC代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <fstream>
using namespace std;typedef long long LL;const int N = 200010;
int a[N], n;bool inline Check()
{static int cnt1, cnt2, sum;cnt1 = cnt2 = sum = 0;for (int i = 1; i <= n; i ++ ){sum += a[i];cnt1 += (a[i] == 1);cnt2 += (a[i] == 2);}if (sum & 1) return false;else if (cnt1 == 0 && cnt2 % 2 == 1) return false;else return true;
}int main()
{int T; cin >> T;while (T -- ){cin >> n;for (int i = 1; i <= n; i ++ ){scanf("%d", &a[i]);}if (Check())cout << "YES\n";elsecout << "NO\n";}return 0;
}
C Long Jumps
原题信息
http://codeforces.com/contest/1472/problem/C
解题思路
一个较为简单的dp,需要注意的是数组下标的特判,防止越界,最好开LL
fif_ifi表示选择iii做为StartPositionStartPositionStartPosition的游戏结果
那么,我们可以得到递归关系式为
————fi=ai+fi+aif_i=a_i+f_{i+a_i}fi=ai+fi+ai 条件为当 i+ai<=ni+a_i<=ni+ai<=n
————否则fi=aif_i=a_ifi=ai
上述的这个讨论就是为了避免数组的越界
AC代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <fstream>
using namespace std;typedef long long LL;const int N = 200010;
int a[N], n;
LL res = 0;
LL f[N];int main()
{int T; cin >> T;while (T -- ){cin >> n;for (int i = 1; i <= n; i ++ ){scanf("%d", &a[i]);}memset(f, 0, sizeof f);res = 0LL;for (int i = n; i >= 1; i -- ){f[i] = a[i] + (i + a[i] <= n ? f[i + a[i]] : 0);res = max(res, f[i]);}/// printf("###########\n");cout << res << endl;}return 0;
}
D Even-Odd Game
原题信息
http://codeforces.com/contest/1472/problem/D
解题思路
题目大意是给定一个数组,AliceBobAlice BobAliceBob轮流在里面选一个数, AliceAliceAlice选择偶数,那么加和偶数一样大的分, 选择奇数不加分;BobBobBob选择奇数,那么加和奇数一样大的分,选择偶数不加分。
从题意出发,我们不难想到下面这种求解方法,将奇数、偶数分开,两个数组分别进行非递增排序,每次选择的时候从两个数组选取最大的,进行拿出。(好像不分开也行)
对于AliceAliceAlice来讲,选取当前最大的数是偶数,那么加分最多,选取最大的数是奇数,是为了防止BobBobBob加分过多!同理,对于BobBobBob也是如此,下面我们来证明他的合法性。
- 直接严格证明是很难的,我们不妨辅助的思想来看这个问题,对于人物A来说,他可以选择自己可以加分的数字,也可以选择不让别人加分的数(自己不加分),不让别人加分,就类似于让自己加分(因为让别人加分少了)
- 也就是说让自己的得分减去另一个人得分更大,自己的赢面更大。因此对于博弈双方,我们只需要选择当前最大数即可。
- 记得开LL
AC代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <fstream>
using namespace std;typedef long long LL;const int N = 200010;
int a[N], n;
int even_a[N], old_a[N], idx_e, idx_o;
LL score_a, score_b;
int cur_o = 1, cur_e = 1;
bool cmp(int a, int b){ return a > b; }bool Check()
{if (cur_o > idx_o) return false;if (cur_e > idx_e) return true;if (old_a[cur_o] >= even_a[cur_e]) return true;else return false;
}
int main()
{int T; cin >> T;while (T -- ){cin >> n;score_a = score_b = 0LL;idx_e = idx_o = 0;for (int i = 1; i <= n; i ++ ){scanf("%d", &a[i]);if (a[i] & 1) old_a[++ idx_o] = a[i];else even_a[++ idx_e] = a[i];}sort(even_a + 1, even_a + idx_e + 1, cmp);sort(old_a + 1, old_a + idx_o + 1, cmp);cur_o = 1, cur_e = 1;for (int i = 1; i <= n; i ++ ){if (Check()) // 奇数{if (i & 1){cur_o ++;}else{score_b += old_a[cur_o ++];}}else // 偶数{if (i & 1){score_a += even_a[cur_e ++];}else{cur_e ++;}}}if (score_a == score_b) puts("Tie");else if (score_a > score_b) puts("Alice");else puts("Bob");}return 0;
}
E Correct Placement
原题信息
解题思路
对于每一个(h,w)(h, w)(h,w)我们需要找到(hi,wi),hi<h,wi<w(h_i, w_i), h_i<h,w_i<w(hi,wi),hi<h,wi<w的下标,至于另一种情况我们直接 swap(h,w)swap(h, w)swap(h,w)即可,
主要是先对(w,h)(w, h)(w,h)进行排序二分查询 hi<hh_i<hhi<h的最大下标,找他的前缀最小值www(可以预处理)
AC代码
#include <bits/stdc++.h>
using namespace std;const int N = 200010;
const int INF = 0x3f3f3f3f;
class Node
{
public:int h, w;int idx;void operator=(const Node &t1){h = t1.h, w = t1.w, idx = t1.idx;}
}a[N], q[N];
int pre_w[N], pre_idx[N];
int n;bool cmp(const Node &t1, const Node &t2)
{if (t1.h == t2.h)return t1.w < t2.w;elsereturn t1.h < t2.h;
}int Cal2(int h, int w)
{int l = 1, r = n, mid;if (h <= q[l].h) return -1;while (l < r){mid = l + r + 1 >> 1;if (q[mid].h < h){l = mid;}else{r = mid - 1;}}if (w > pre_w[l]) // 找到了一个高度小,宽度也小的return pre_idx[l];else // 没有宽度小的return -1;
}int Cal(int h, int w)
{int res1 = Cal2(h, w), res2 = Cal2(w, h);if (res1 == -1 && res2 == -1)return -1;elsereturn max(res1, res2);
}int main()
{int t; cin >> t;while (t -- ){scanf("%d", &n);for (int i = 1; i <= n; i ++ ){scanf("%d%d", &a[i].h, &a[i].w);a[i].idx = i;q[i] = a[i];}sort(q + 1, q + 1 + n, cmp);pre_w[0] = INF, pre_idx[0] = -1;for (int i = 1; i <= n; i ++ ){if (q[i].w < pre_w[i - 1]){pre_w[i] = q[i].w, pre_idx[i] = q[i].idx;}else{pre_w[i] = pre_w[i - 1], pre_idx[i] = pre_idx[i - 1];}}/*for (int i = 1; i <= n; i ++ )printf("h=%d, w=%d, idx=%d, pre_w=%d, pre_idx=%d\n", q[i].h, q[i].w, q[i].idx, pre_w[i], pre_idx[i]);*/printf("%d", Cal(a[1].h, a[1].w));for (int i = 2; i <= n; i ++ ){printf(" %d", Cal(a[i].h, a[i].w));}puts("");}return 0;
}
F New Year’s Puzzle
原题信息
http://codeforces.com/contest/1472/problem/F
解题思路
首先第一个想到的就应该是对这些点按照列编号进行排序,然后我们逐个点进行处理。
用cur1,cur2cur1,cur2cur1,cur2表示当前第一行,第二行当前可以放置的最小列编号。下面我们对BlockedCellBlocked CellBlockedCell进行讨论
- r1=r2r1 = r2r1=r2,即两个BlockedCellBlockedCellBlockedCell将一整列堵住,那么要求abs(cur1−cur2)abs(cur1-cur2)abs(cur1−cur2)%2=02=02=0,最后更新cur1=cur2=r1+1cur1=cur2=r1+1cur1=cur2=r1+1即可,这一次处理了两个BlockedCellBlockedCellBlockedCell问题
- r1!=r2r1 != r2r1!=r2,我们处理BlockedCell(r1,c1)BlockedCell(r1, c1)BlockedCell(r1,c1),假设这个BlockedCellBlockedCellBlockedCell在cur1cur1cur1那一列。首先倘若r1<cur1r1<cur1r1<cur1指定不行,因为小于cur1cur1cur1的区域已经使用完毕
那么如果(r1−cur2)(r1-cur2)(r1−cur2)%2=02=02=0,那么就说明这一行是可以自己解决的。
即使是使用了下面的那一行,也会是成对的使用,与另外一列自己解决一样
倘若(r1−cur2)(r1-cur2)(r1−cur2)%2=12=12=1,也就是说他需要另外一列的帮助,加奇数的竖线排列,下面我们只需考虑是否可以加(至少)一个竖线,在这里只需要考虑加一个就行,因为多加了也只能是奇数个,奇数个竖线,两两可以合并为横线,因为是加入一个,我们考虑一下最晚加入(在cur−1cur-1cur−1的位置)。
为什么是最晚在cur−1cur-1cur−1?因为最晚就是在 BlockedCell之前加入!
那么如何判断是否可以,那么只需在最晚加入的话,另一个列前方时候可以组成成对个二元组即可
最后cur1,cur2cur1, cur2cur1,cur2注意更新,一个为c1c1c1,一个为c1+1c1 + 1c1+1 - 最后,我们还需考虑满足了BlockedCellBlockedCellBlockedCell之后的cur1,cur2cur1, cur2cur1,cur2是否可以在结尾处ok,因此检验一下
(cur1−cur2)(cur1-cur2)(cur1−cur2)%2=02=02=0->成立,否则不成立
AC代码
#include <bits/stdc++.h>
using namespace std;const int M = 200010, INF = 0x3f3f3f3f;
int n, m;class Node
{
public:int r, c;
}q[M];bool cmp(const Node &t1, const Node &t2)
{if (t1.c == t2.c)return t1.r < t2.r;elsereturn t1.c < t2.c;
}bool Check()
{// if (m & 1) return false;sort(q + 1, q + 1 + m, cmp);
/*for (int i = 1; i <= m; i ++ )printf("I=%d, r=%d, c=%d\n", i, q[i].r, q[i].c);
*/int r1, r2, c1, c2;int cur1 = 1, cur2 = 1;q[m + 1].c = INF;for (int i = 1; i <= m; i += 1){/// printf("I=%d, cur1=%d, cur2=%d\n", i, cur1, cur2);c1 = q[i].c, r1 = q[i].r, c2 = q[i + 1].c, r2 = q[i + 1].r;if (c1 == c2){/// puts("Equal");i ++;if (abs(cur1 - cur2) & 1) // 口封不上return false;cur1 = cur2 = c1 + 1;}else{if (r1 == 1) // 上面{/// puts("Up");if (cur1 > c1) // 当前这个地方不能堵上return false;else{if ((c1 - cur1) % 2 == 0) // 偶数,可以自行解决{cur1 = c1 + 1;}else // 需要下面协助一个竖着的{cur1 = c1 - 1;if (cur1 - cur2 >= 0 && (cur1 - cur2) % 2 == 0) // 下面可以跟上{cur1 = c1 + 1;cur2 = c1;}else{return false;}}}}else // 下面{/// puts("Down");if (cur2 > c1) // 当前这个地方不能堵上return false;else{if ((c1 - cur2) % 2 == 0) // 偶数,可以自行解决{cur2 = c1 + 1;}else // 需要上面协助一个竖着的{cur2 = c1 - 1;/*printf("Down, cur1=%d, cur2=%d\n", cur1, cur2);printf("%d\n", cur2 - cur1 >= 0);printf("%d\n", ((cur2 - cur1) % 2 == 0));*/if ((cur2 - cur1 >= 0) && ((cur2 - cur1) % 2 == 0)) // 上面可以跟上{cur2 = c1 + 1;cur1 = c1;}else{//puts("FALSE");return false;}}}}}}if (abs(cur1 - cur2) & 1)return false;elsereturn true;
}int main()
{int t; cin >> t;while (t -- ){scanf("%d%d", &n, &m);for (int i = 1; i <= m; i ++ )scanf("%d%d", &q[i].r, &q[i].c);if (Check())puts("YES");elseputs("NO");}return 0;
}
/*
1
4 2
1 2
2 2
YES
*/
/*
1
4 3
1 1
1 2
2 2
NO
*/
/*
1
4 2
1 1
2 3
YES
*/
/*
3
5 2
2 2
1 4
3 2
2 1
2 3
6 4
2 1
2 3
2 4
2 6
*/
G Moving to the Capital
原题信息
http://codeforces.com/contest/1472/problem/G
解题思路
一个很有意思的dp问题
首先我们先利用bfs求出起点到其它任意各点的举例,然后我们进行dp求解。
-
dp思路一
fi,0f_{i, 0}fi,0表示从iii点开始,使用0次规则2可以达到的最小ddd,(其实就是自身的ddd)
fi,1f_{i, 1}fi,1表示从iii点开始,使用1次规则2可以达到的最小ddd
下面我们来分析一下fi,1f_{i, 1}fi,1如何进行更新,
fi,1={fj,0∃i⇒j边且di≥djfj,1∃i⇒j边且di<djf_{i, 1}=\begin{cases} f_{j, 0} & \exists i \Rightarrow j 边且d_i\geq d_j\\ f_{j, 1} & \exists i \Rightarrow j 边且d_i < d_j\\ \end{cases}fi,1={fj,0fj,1∃i⇒j边且di≥dj∃i⇒j边且di<dj
fj,0f_{j, 0}fj,0容易保证,因为直接就是djd_jdj,但是我们如何保证使用的fj,1f_{j,1}fj,1是更新完毕的最终结果呢? 下面我们考虑fj,1f_{j,1}fj,1如何使其尽可能早的确定,而且不会使用到未确定的fj,1f_{j,1}fj,1,我们从di,djd_i, d_jdi,dj在分段函数的关系中,就可以发现,倘若我们对ddd的距离进行降序排序,即先进性距离更大的dpdpdp,那么就可以是得使用到的fj,1f_{j,1}fj,1都会是前面进行处理完毕的,因此进行dpdpdp的算法如下代码 -
Algo
-
首先bfsbfsbfs处理出各点的距离did_idi
-
处理出did_idi之后,我们就可以进行按照距离进行非递增排序。
-
按照分段函数进行dpdpdp
AC代码
#include <bits/stdc++.h>
using namespace std;const int N = 200010, INF = 0x3f3f3f3f;
int f[N];
int h[N], e[N], ne[N], idx;
int n, m;
bool st[N];
int dist[N];
vector<int> order;void Initial()
{// edge initialmemset(h, -1, sizeof h);idx = 0;// dp initialmemset(f, 0x3f, sizeof f);// dist initialmemset(dist, -1, sizeof dist);// orderorder.clear();
}void add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}int main()
{int t; cin >> t;while (t -- ){// inputscanf("%d%d", &n, &m);// initialInitial();for (int i = 1, u, v; i <= m; i ++ ){scanf("%d%d", &u, &v);add(u, v);}// bfs for distqueue<int> que;que.push(1); dist[1] = 0;order.push_back(1);int u, v;while (que.size()){u = que.front(); que.pop();for (int i = h[u]; ~i; i = ne[i]){v = e[i];if (dist[v] == -1){dist[v] = dist[u] + 1;que.push(v);order.push_back(v);}}}/*for (int i = 1; i <= n; i ++ )printf("i=%d dist=%d\n", i, dist[i]);*/// dfs for dp/*从距离最远的开始,进行枚举dp*/for (int j = n - 1, u, v; j >= 0; j -- ){u = order[j];f[u] = dist[u];for (int i = h[u]; ~i; i = ne[i]){v = e[i];if (dist[u] >= dist[v]) // u -> v 需要耗费一次机会, 只可以 取dist{f[u] = min(f[u], dist[v]);}else // 不需要消耗机会,直接最优子结构{f[u] = min(f[u], f[v]);}}}// output/*for (int i = 1; i <= n; i ++ )printf("i=%d, f[i][0]=%d, f[i][1]=%d\n", i, f[i][0], f[i][1]);*/printf("%d", 0);for (int i = 2; i <= n; i ++ )printf(" %d", f[i]);puts("");}return 0;
}