http://codeforces.com/contest/1161
A. Hide and Seek
题意:有n个位置,Alice在某个位置放了一个球,Bob猜m次,每次猜之前或之后Alice可以把球换到相邻的位置,但是整个过程最多只能换一次,要Bob一次都猜不对,问有几种初始放球和最终球位置的情况数。
判断每个位置作为初始位置,能不能向左或向右交换,或者不动。
仅当交换之后Bob的序列里没有换完之后的位置才能交换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <bits/stdc++.h> using namespace std;#define ll long long const int N = 2e5 + 10 ;const int INF = 0x3f3f3f3f ;int n, k, a[N], l[N], r[N];ll ans; int main () { scanf ("%d%d" , &n, &k); memset (l, 0x3f , sizeof (l)); for (int i = 1 ; i <= k; i++) { scanf ("%d" , &a[i]); l[a[i]] = min (l[a[i]], i); r[a[i]] = max (r[a[i]], i); } for (int i = 1 ; i <= n; i++) { if (r[i]) { if (i < n&&r[i + 1 ] < l[i])ans++; if (i > 1 && r[i - 1 ] < l[i])ans++; } else { ans++; if (i > 1 )ans++; if (i < n)ans++; } } printf ("%lld\n" , ans); return 0 ; }
题意:有一个圆被n个点均分成n份,点之间有一些连边,问连出的图形是否是旋转对称图形,即旋转完某个角度与原图相同。
对于一个角度,只要判断所有连边的端点旋转后是否仍然连边。
但是枚举所有角度显然超时。
但是旋转对称只要枚举n的因子即可,这个结论已经遇到几次了。
旋转角度a后对称等价于旋转角度gcd(a,n)后对称。
充分性:旋转a相当于整数个gcd(a,n),所以旋转角度gcd(a,n)后对称 ⟹ \implies ⟹ 旋转角度a后对称.
必要性:旋转角度n,即360度,一定等于原图,所以可以旋转任意次n。斐蜀定理得xa+yn=gcd(a,n)一定有解,所以一定可以通过旋转n和旋转a这两种操作得到旋转gcd(a,n),每次旋转操作完都等于原图,那么最终一定也等于原图,即旋转gcd(a,n)也对称。所以旋转角度a后对称 ⟹ \implies ⟹ 旋转角度gcd(a,n)后对称.
即只要枚举n的因子,每次 O ( m ) O(m) O ( m ) 检查。复杂度 O ( m n ) O(m\sqrt n) O ( m n ) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> using namespace std;#define ll long long const int N = 2e5 + 10 ;const int INF = 0x3f3f3f3f ;int n, m;typedef pair<int , int >pii;map<pii, int >mp; vector<pii>vc; bool check (int x) { for (pii p : vc) { int u = p.first, v = p.second; if (!mp.count (pii ((u + x) % n, (v + x) % n)))return false ; } return true ; } int main () { scanf ("%d%d" , &n, &m); for (int i = 1 ; i <= m; i++) { int u, v; scanf ("%d%d" , &u, &v); u--; v--; mp[pii (u, v)] = 1 ; mp[pii (v, u)] = 1 ; vc.push_back (pii (u, v)); } for (int i = 1 ; i < n; i++) { if (n%i)continue ; if (check (i)) { puts ("Yes" ); return 0 ; } } puts ("No" ); return 0 ; }
C. Thanos Nim
题意:有n堆石子,两个人轮流操作,每次选n/2堆,从每堆各拿正数个石子,先没法拿得人输,问先手还是后手赢。
博弈
结论:设各堆最少石子数a,则若等于a的堆数大于n/2,先手必败,否则后手必败。
可以发现先拿空至少一堆的人必败。因为另一个人可以拿完n/2堆,而再轮到我时就没法找到n/2堆了。
若等于a的堆数大于n/2,则先手一定会选到至少一堆有最少的石子,这样最少的石子数就一定会减小,后手再把n/2堆拿到剩下新的最少石子数,这样始终保持最少石子数堆数大于n/2,且最少石子数不断减小,最终一定是先手先拿完一堆,则先手败。
若等于a的堆数小于等于n/2,则先手一定可以选择不等于最少石子数的n/2堆拿,拿到剩下最少石子数,使得等于最少石子数的堆数大于n/2,使得后手面临情况1,则后手败。
至于为什么能想到,可能还是和n/2这个特殊的数字有关吧,以及先拿空的人必败这个结论,每次至少拿一个的限制使得最少石子数一定在减小,迫使一方先拿空。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <bits/stdc++.h> using namespace std;#define ll long long const int N = 2e5 + 10 ;const int INF = 0x3f3f3f3f ;int n;int a[N], mn = INF, cnt;int main () { scanf ("%d" , &n); for (int i = 0 ; i < n; i++)scanf ("%d" , &a[i]), mn = min (mn, a[i]); for (int i = 0 ; i < n; i++)if (a[i] == mn)cnt++; puts ((cnt > n / 2 ) ? "Bob" : "Alice" ); return 0 ; }