先来一个大神的全题解:http://blog.myungwoo.kr/121
A. Broadcast Stations
B. Connect3
题意:在一个4*4的棋盘上玩游戏,先手执黑,后手执白。每次一位玩家选一个未填满的column,在其中row id最小的位置放下自己的棋子。如果连续的三个棋子同色(同行/同列/同对角线),游戏结束,下最后一步的人获胜。下面给你一组询问,(x, a, b) (1 <= x, a, b <= 4),问你先手第一步下(1, x),且游戏的最后一步是后手下白棋在(a, b)时,棋盘的最终形态有多少种。
思考:发现可以用三进制数(3^16)表示棋盘,0表示空,1表示黑棋,2表示白棋。暴力搜索即可。如果是多组询问,还可以把4*4*4中询问的结果本地打表。
代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long ll; 5 typedef pair<int, int> ii; 6 typedef pair<ll, ll> l4; 7 typedef pair<ii, int> iii; 8 #define mp make_pair 9 #define pb push_back 10 11 const int N = 43046721; 12 int p[16]; 13 int ans[4][4][4]={0}; 14 int d[N]={0}; 15 int x; 16 inline int getp(int x, int y) 17 { 18 return p[x*4+y]; 19 } 20 inline int get(const int &bit, int x, int y) 21 { 22 int a = x*4+y; 23 return bit/p[a]%3; 24 } 25 inline bool check(const int &bit, int x, int y, int win=2, bool print=false) 26 { 27 if (print) 28 cerr << "check " << x << " " << y << " " << win << endl; 29 //check vertical 30 bool work; 31 32 for (int i = max(0, x-2); i <= min(x, 1); ++i) 33 { 34 work = true; 35 for (int j = 0; j < 3; ++j) 36 if (get(bit, i+j, y) != win) 37 { 38 work = false; 39 break; 40 } 41 if (work) 42 { 43 if (print) cerr << i << " to " << x+2 << " at y = " << y << endl; 44 return true; 45 } 46 } 47 //check horizontal 48 for (int i = max(0, y-2); i <= min(y, 1); ++i) 49 { 50 work = true; 51 for (int j = 0; j < 3; ++j) 52 if (get(bit, x, i+j) != win) 53 { 54 work = false; 55 break; 56 } 57 if (work) 58 { 59 if (print) cerr << "at x = "<< x << " " << i << " to " << i+2 << endl; 60 return true; 61 } 62 } 63 //check dia 64 int tx = x, ty = y; 65 while (tx > 0 && ty > 0) 66 --tx, --ty; 67 while (tx+2 < 4 && ty+2<4) 68 { 69 work = true; 70 for (int j = 0; j < 3; ++j) 71 if (get(bit, tx+j, ty+j) != win) 72 { 73 work = false; 74 break; 75 } 76 if (work) 77 { 78 if (print) cerr << tx << " to " << tx+2 << " and " << ty << " " << ty+2 << endl; 79 return true; 80 } 81 ++tx, ++ty; 82 } 83 //check anti dia 84 tx = x, ty = y; 85 while (tx > 0 && ty < 3) 86 --tx, ++ty; 87 while (tx+2<4 && ty-2>=0) 88 { 89 work = true; 90 for (int j = 0; j < 3; ++j) 91 if (get(bit, tx+j, ty-j) != win) 92 { 93 work = false; 94 break; 95 } 96 if (work) 97 { 98 if (print) cerr << tx << " to " << tx+2 << " and " << ty << " " << ty-2 << endl; 99 return true; 100 } 101 ++tx, --ty; 102 } 103 return false; 104 } 105 void print(int cur) 106 { 107 return; 108 cerr << endl << "print "; 109 for (int i = 0; i < 4; ++i, cerr << endl) 110 for (int j = 0; j < 4; ++j) 111 { 112 cerr << get(cur, i, j); 113 } 114 } 115 int solve(int xx, int aa, int bb) 116 { 117 int ret = 0; 118 x = xx; 119 memset(d, 0, sizeof(d)); 120 queue<int> q, nq; 121 int cur = 0; 122 cur = 1*getp(0, xx); 123 q.push(cur); 124 d[cur] = -1; 125 set<int> st; 126 while (!q.empty()) 127 { 128 while (!q.empty()) 129 { 130 int cur = q.front(); 131 q.pop(); 132 if (get(cur, aa, bb) != 0) 133 continue; 134 135 for (int j = 0; j < 4; ++j) 136 for (int i = 0; i < 4; ++i) 137 if (get(cur, i, j) == 0) 138 { 139 // cerr << "can go " << i << "," << j << endl; 140 int nxt = cur + 2 * getp(i, j); 141 if (check(nxt, i, j)) 142 { 143 if (i == aa && j == bb) 144 { 145 st.insert(nxt); 146 } 147 } 148 else 149 { 150 if (d[nxt]) 151 break; 152 else 153 { 154 d[nxt] = -1; 155 nq.push(nxt); 156 } 157 } 158 break; 159 } 160 } 161 // cerr << "nq "; 162 while (!nq.empty()) 163 { 164 int cur = nq.front(); 165 nq.pop(); 166 if (get(cur, aa, bb) != 0) 167 continue; 168 169 for (int j = 0; j < 4; ++j) 170 for (int i = 0; i < 4; ++i) 171 if (get(cur, i, j) == 0) 172 { 173 int nxt = cur + getp(i, j); 174 if (d[nxt]) 175 break; 176 if (check(nxt, i, j, 1)) 177 break; 178 d[nxt] = -1; 179 q.push(nxt); 180 break; 181 } 182 } 183 } 184 /* 185 for (int i = 0; i < 200; ++i) 186 { 187 cerr << i << endl; 188 check(*st.begin(), aa, bb, 2, true); 189 print(*st.begin()); 190 st.erase(st.begin()); 191 } 192 */ 193 return st.size(); 194 } 195 int main() 196 { 197 p[0] = 1; 198 for (int i = 1; i < 16; ++i) 199 p[i] = p[i-1]*3; 200 assert(N == p[15]*3); 201 int a, b, c; 202 scanf("%d %d %d", &c, &a, &b); 203 printf("%d ", solve(c-1, a-1, b-1)); 204 }
赛后:
比赛中写的很麻烦,主要是因为一开始读错了题,没有看到每一步只能下在一个column的最低位置。现在想想,可不可以直接枚举棋盘的终止状态,判断(a, b)是否可以是最后一步即可。
但其实实现发现有很多细节需要考虑,主要是检查当前棋盘是否是一个合法的游戏状态。最好的方法还是由合法状态bfs到其他状态
C. Game Map
题意:n <= 1e5 个点,m <= 3e5 条边的无向图。从任意一点出发,只能向度数比当前点的度数大的点走。求最长的路径上有多少个点。
观察:要求路径上点度数严格递增,其实已经给边定了方向且无环。所以本质就是DAG上的最长路,记忆画搜索即可。
代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long ll; 5 typedef pair<int, int> ii; 6 typedef pair<ll, ll> l4; 7 typedef pair<ii, int> iii; 8 #define mp make_pair 9 #define pb push_back 10 11 12 13 const int maxn = 1e5+1; 14 int n, m; 15 vector<int> g[maxn]; 16 int d[maxn]; 17 18 int dp(int cur) 19 { 20 int &ret = d[cur]; 21 if (ret == -1) 22 { 23 ret = 1; 24 for (int i = 0; i < g[cur].size(); ++i) 25 { 26 int nxt = g[cur][i]; 27 if (g[nxt].size() > g[cur].size()) 28 ret = max(ret, 1+dp(nxt)); 29 } 30 } 31 return ret; 32 } 33 34 35 int main() 36 { 37 scanf("%d %d", &n, &m); 38 for (int i = 0; i < m; ++i) 39 { 40 int a, b; 41 scanf("%d %d", &a, &b); 42 g[a].pb(b); 43 g[b].pb(a); 44 } 45 memset(d, -1, sizeof(d)); 46 int ans = 1; 47 for (int i = 0; i < n; ++i) 48 ans = max(ans, dp(i)); 49 printf("%d ", ans); 50 }
D. Happy Number
题意:f(n) = n各数位平方的和。如果存在一个k使得f^k(n) = 1, 那么就说n是happy的。可以发现,如果n不是happy的,那么一定存在一个p使得f^p(n) = n。下面给你一个询问n<=1e9,让你输出他是否happy。
观察:对于n <= 1e9, f(n) <= max(9*9^2, f(1e9)) = 9^3。直接暴力计算,遇到循环终止。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 5 bool vis[1000]; 6 int f(int n) 7 { 8 int ret = 0; 9 while (n) 10 { 11 int tmp = n%10; 12 n /= 10; 13 ret += tmp * tmp; 14 } 15 return ret; 16 } 17 bool solve(int n) 18 { 19 if (n == 1) 20 return true; 21 if (vis[n]) 22 return false; 23 vis[n] = true; 24 return solve(f(n)); 25 } 26 int main() 27 { 28 memset(vis, 0, sizeof(vis)); 29 int n; 30 scanf("%d", &n); 31 n = f(n); 32 puts(solve(n)?"HAPPY":"UNHAPPY"); 33 }
E. How Many to Be Happy
题意:边带权无向图G,对于一条边e,如果存在一个包含e的最小生成树,那么e就是happy的。定义H(e) 为 使e变为happy所需删掉的最少边数。输入一个n <= 100个点m<=500条边的图,让你输出所有边的H(e)之和。
观察:对于一条边e(x, y, len),只有长度小len的边才会对H(e)产生影响(等于len的边不影响,可以考虑kruskal算法中,边权相同的边的位置可以随便更改)。而且边数点数都不多,也许可以每条边分别处理。那么考虑所有长度小于len的边的生成子图SubG,若想要e存在于某个mst,就要删掉最小的边使SubG中x和y不联通,可以发现就是要求最小割。
F. Philosphoer's Walk
题意:
观察:大体可以看出是个recursion。
G. Rectilinear Regions
题意:给你两条阶梯线U和L,阶梯线是指非递增或者非递减的折线。每条阶梯线的拐点个数不超过25e3。保证两条直线的拐点没有重复的x或者y,让你求出U在上方L在下方的封闭空间的个数和面积和。
观察:首先如果L和U的方向不同(一增一减),直接输出“0 0”。同减的情况可以转化成同增,方法是先把两条边上所有的y取相反数,然后交换U和L两条线。这样只需要考虑同增的情况。用一个sorted vector来保存拐点就好。小心一开始U就高于L的特殊情况,还有小心最后U高于L的情况。第一个特殊情况我是用一个bool first = true来标记,当L高于U的时候first = false,只有当first == false的时候才开始计算答案。第二个特殊情况我是记录一个tmparea,表示当前空间的面积,每当L高于U使得面积闭合的时候,我在吧tmparea更新到答案(同时更新封闭区间的个数)。
代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long ll; 5 typedef pair<int, int> ii; 6 typedef pair<ll, ll> l4; 7 typedef pair<ii, int> iii; 8 #define mp make_pair 9 #define pb push_back 10 11 12 const int N = 25e3+10; 13 struct Node 14 { 15 int x, y, id; 16 bool operator<(const Node &r) const 17 { 18 return x < r.x; 19 } 20 void read(int xx) 21 { 22 scanf("%d %d", &x, &y); 23 id =xx ; 24 } 25 } a[N], b[N], c[N<<1]; 26 27 28 int main() 29 { 30 int n, m; 31 int y0, y1; 32 scanf("%d %d", &n, &m); 33 scanf("%d", &y0); 34 for (int i = 0; i < n; ++i) 35 a[i].read(0); 36 scanf("%d", &y1); 37 for (int i = 0; i < m; ++i) 38 b[i].read(1); 39 bool d1 = a[0].y > y0; 40 bool d2 = b[0].y > y1; 41 if (d1 != d2) 42 { 43 puts("0 0"); 44 return 0; 45 } 46 else if (d1 == 0) 47 { 48 y0 *= -1; 49 y1 *= -1; 50 for (int i = 0; i < n; ++i) 51 a[i].y *= -1, a[i].id = 1; 52 for (int j = 0; j < m; ++j) 53 b[j].y *= -1, b[j].id = 0; 54 swap(y0, y1); 55 swap(a, b); 56 swap(n, m); 57 } 58 59 // cerr << "read done "; 60 merge(a, a+n, b, b+m, c); 61 int last=-1; 62 ll ans = 0; 63 ll tmp = 0; 64 int ansans = 0; 65 bool first = y1 > y0; 66 for (int i = 0; i < n+m; ++i) 67 { 68 if (c[i].id == 0) 69 { 70 if (last != -1) 71 { 72 tmp += 1ll*(c[i].x-last)*(y1-y0); 73 if (c[i].y < y1) 74 last = c[i].x; 75 else 76 last = -1, ++ansans, ans += tmp, tmp = 0; 77 } 78 y0 = c[i].y; 79 if (y0 >= y1) 80 first = false; 81 } 82 else 83 { 84 if (last != -1) 85 { 86 tmp += 1ll*(c[i].x-last)*(y1-y0); 87 last = c[i].x; 88 } 89 y1 = c[i].y; 90 if (!first && last == -1 && y1 > y0) 91 { 92 last = c[i].x; 93 } 94 } 95 // cerr << c[i].id << " " << c[i].x << " " << c[i].y << " " << c[i].id <<" " << y0 << " " << y1 << " " << last << endl; 96 } 97 printf("%d %lld ", ansans, ans); 98 }
H. Rock Paper Scissors
大坑
I. Slot Machines
题意:给你一个长度为n<= 1e6的整数序列T[1-n],找到使得pair(k+p, p)最小的k和p,k和p要满足, T[i+p] = T[i] (k < i <= n-p)。
方法:观察可知,对于一组(k, p),p为T[k+1, ..., n] 的周期,取最小的循环周期最优。而通过kmp可以O(n)预处理fail数组f[] 然后O(1)询问任意前缀的循环节长度(i-f[i]),所以只需要对T[1-n]反向求kmp得到fail数组,枚举k,p可以快速求出,更新答案。
代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long ll; 5 typedef pair<int, int> ii; 6 typedef pair<ll, ll> l4; 7 typedef pair<ii, int> iii; 8 #define mp make_pair 9 #define pb push_back 10 11 12 13 14 const int maxn = 1e6+10; 15 int a[maxn], f[maxn], n; 16 int main() 17 { 18 scanf("%d", &n); 19 for (int i = n-1; i >= 0; --i) 20 { 21 scanf("%d", a+i); 22 } 23 f[0] = -1; 24 for (int i = 0, j = -1; i < n; ++i, ++j) 25 { 26 while (j != -1 && a[j] != a[i]) 27 j = f[j]; 28 f[i+1] = j+1; 29 } 30 int k = n, p = 1; 31 for (int i = n; i >= 1; --i) 32 { 33 int kk = n-i; 34 int pp = i-f[i]; 35 if (pp + kk < k+p) 36 k = kk, p = pp; 37 else if (pp + kk == k+p && pp < p) 38 k = kk, p = pp; 39 } 40 printf("%d %d ", k, p); 41 }
J. Strongly Matchable
K. Untangling Chain
题意:给你一个有n-1个拐点的折线 (n <= 1e5),要求你妥善安排每一个线段的长度,使得折线段不会自交。每一条线段的长度不能超过n。
方法:如果n不是很大,可以使第i段线段的长度为2^i,这样一定不会自交。此时思路变成,可不可以使边长不断增大,使得后续的转弯不会受之前的影响。
想了想可不可以直接把线段长度设成1-n,发现不行,比如假设从(0, 0)开始 右(1, 0) 上(1, 2) 右(1+3, 2) 上(1+3, 2+4) 右(1+3+5, 2+4) 下(1+3+5, 2+4-6) 左(1+3+5-7, 0)自交了。即如果在水平方向上(竖直方向类似),如果连续的几次运动都是朝着同一个自方向,那么我们的方案会使得这个方向的距离快速增加(即上述水平方向上连续三次向右的操作,总的看是连续向右了1+3+5 = 9个单位),那么下一次向反方向的操作(如上述的向左),就需要一个很大的长度(上述例子中至少需要9+1)。
然后就想到了正确的做法。分开考虑水平方向和竖直方向。对于一个方向,考虑这个方向上第i次移动,如果方向和上一次相同,那么就走1的长度,不然就走i的长度。可以发现这样走,不管怎么样都不会自交。例子: 右上右上右下左 : 右(1, 0)上(1, 1)右(1+1, 1)上(2, 1+1)右(2+1, 2)下(3, 2-3)左(3-4, -1)
代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn=10005; 4 const int dx[4]={0,0,-1,1}; 5 const int dy[4]={-1,1,0,0}; 6 int n,dir[maxn],s,lstx,lsty,res[maxn],ansx=1,ansy=1; 7 inline void change(int dir, int &dx, int &dy) 8 { 9 if (dir == 1) 10 { 11 int ddx = -dy; 12 int ddy = dx; 13 dy = ddy; 14 dx = ddx; 15 } 16 else 17 { 18 int ddx = dy; 19 int ddy = -dx; 20 dy = ddy; 21 dx = ddx; 22 } 23 } 24 int main() 25 { 26 scanf("%d",&n); 27 for (int i=0;i<n;++i) 28 scanf("%d%d",&s,&dir[i]); 29 if (n==1) 30 puts("1"); 31 else if (n==2) 32 puts("1 2"); 33 else { 34 int dx = 1, dy = 0; 35 res[0] = 1; 36 int last = 1; 37 int cal = 1; 38 for (int i = 1; i < n-1; i += 2) 39 { 40 change(dir[i-1], dx, dy); 41 change(dir[i], dx, dy); 42 ++cal; 43 if (dx == last) 44 { 45 res[i+1] = 1; 46 } 47 else 48 { 49 res[i+1] = cal; 50 last = dx; 51 } 52 } 53 dx = 1, dy = 0; 54 change(dir[0], dx, dy); 55 last = dy; 56 cal = 1; 57 res[1] = 1; 58 for (int i = 2; i < n-1; i += 2) 59 { 60 change(dir[i-1], dx, dy); 61 change(dir[i], dx, dy); 62 ++cal; 63 if (last == dy) 64 res[i+1] = 1; 65 else 66 res[i+1] = cal, last = dy; 67 } 68 res[n-1] = 1; 69 /* 70 lstx=dir[0],lsty=dir[1]; 71 res[0]=res[1]=1; 72 for (int i=1;i<n;++i) { 73 if (i%2==0) { 74 ++ansx; 75 if (dir[i]==lstx) 76 res[i]=1; 77 else 78 res[i]=ansx,lstx=-lstx; 79 } else { 80 ++ansy; 81 if (dir[i]==lsty) 82 res[i]=1; 83 else 84 res[i]=ansy,lsty=-lsty; 85 } 86 } 87 */ 88 for (int i=0;i<n;++i) 89 printf("%d%c",res[i],i+1==n?' ':' '); 90 } 91 return 0; 92 }
L. Vacation Plans
题意: