题目链接
http://codeforces.com/gym/101667
参考题解
B - Connect3
简要题意:
给定 4 × 4 4×4 4×4 的棋盘玩 C o n n e c t 3 Connect 3 Connect3,问先手落子在 ( 1 , x ) (1, x) (1,x) 且后手以落子 ( a , b ) (a, b) (a,b) 赢得游戏的不同棋盘状态个数。
解题思路:
棋盘很小,直接搜索,需要对棋盘状态进行压缩,最后去重输出个数。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 5e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
set<int> st;
int a[5][5], pw[233];
int sy, tx, ty, ans;
int isEnd(){
for(int i = 1; i <= 4; ++i){
for(int j = 1; j <= 2; ++j){
if(a[i][j] && a[i][j] == a[i][j + 1] && a[i][j] == a[i][j + 2]) return 1;
}
}
for(int j = 1; j <= 4; ++j){
for(int i = 1; i <= 2; ++i){
if(a[i][j] && a[i][j] == a[i + 1][j] && a[i][j] == a[i + 2][j]) return 1;
}
}
for(int i = 1; i <= 2; ++i){
for(int j = 1; j <= 2; ++j){
if(a[i][j] && a[i][j] == a[i + 1][j + 1] && a[i][j] == a[i + 2][j + 2]) return 1;
}
}
for(int i = 3; i <= 4; ++i){
for(int j = 1; j <= 2; ++j){
if(a[i][j] && a[i][j] == a[i - 1][j + 1] && a[i][j] == a[i - 2][j + 2]) return 1;
}
}
return 0;
}
int getR(int c){
for(int i = 1; i <= 4; ++i){
if(!a[i][c]) return i;
}
return 0;
}
int getSta(){
int sta = 0;
for(int i = 1; i <= 4; ++i){
for(int j = 1; j <= 4; ++j){
int t = a[i][j] == -1 ? 2 : a[i][j];
sta += t * pw[(i - 1) * 4 + j - 1];
}
}
return sta;
}
void dfs(int p, int x, int y, int res){
if(isEnd()){
if(p == 1 && x == tx && y == ty) st.emplace(getSta());
return;
}
if(!res) return;
for(int i = 1; i <= 4; ++i){
int r = getR(i);
if(!r) continue;
a[r][i] = p;
dfs(-p, r, i, res - 1);
a[r][i] = 0;
}
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
pw[0] = 1;
for(int i = 1; i <= 16; ++i) pw[i] = pw[i - 1] * 3;
// cout << pw[16] << endl;
cin >> sy >> tx >> ty;
a[1][sy] = 1;
dfs(-1, 1, sy, 15);
cout << sz(st) << endl;
return 0;
}
C - Game Map
简要题意:
给定 n n n 个顶点、 m m m 条边的无向图,一条从 s s s 到 t t t 的路径需要满足顶点度数递增,问最长的路径长度。
解题思路:
转移是从度数低到高的转移,可拓扑,按拓扑序 d p dp dp。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 5e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
vector<int> G[maxn];
pii a[maxn];
int deg[maxn], dp[maxn];
int n, m;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= m; ++i){
int u, v; cin >> u >> v; ++u, ++v;
G[u].pb(v), G[v].pb(u);
++deg[u], ++deg[v];
}
for(int i = 1; i <= n; ++i){
a[i] = {deg[i], i};
}
sort(a + 1, a + 1 + n);
int ret = 0;
for(int i = 1; i <= n; ++i){
int u = a[i].second; dp[u] = 1;
for(auto &v : G[u]){
if(deg[v] < deg[u]) dp[u] = max(dp[u], dp[v] + 1);
}
ret = max(ret, dp[u]);
}
cout << ret << endl;
return 0;
}
D - Happy Number
简要题意:
签到题。
解题思路:
大力模拟。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 5e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
set<ll> st;
ll n;
ll op(ll x){
ll ret = 0;
while(x > 0){
ret += x % 10 * (x % 10);
x /= 10;
}
return ret;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
while(n != 1 && !st.count(n)){
st.emplace(n);
n = op(n);
// cout << n << endl;
}
cout << (n == 1 ? "HAPPY" : "UNHAPPY") << endl;
return 0;
}
E - How Many to Be Happy?
简要题意:
给定 n n n 个顶点、 m m m 条带权边的无向图,定义 H ( e ) H(e) H(e) 为让边 e e e 成为某一 M S T MST MST 的边所需要删掉的原图的最少边数,求 ∑ H ( e i ) \sum H(e_i) ∑H(ei)。
解题思路:
边 e ( u , v ) e(u, v) e(u,v) 作为 M S T MST MST 的边,则需要满足 u → v u \rightarrow v u→v 的环上不存在比 e ( u , v ) e(u, v) e(u,v) 边权更小的边,否则在环上可以用边权更小的边替换 e ( u , v ) e(u, v) e(u,v)。故求 H ( e ) H(e) H(e) 时,将边权比 e e e 小的边拿出来建图,在图上跑 u → v u \rightarrow v u→v 的最小割即为答案。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
struct Edge{
int u, v, w;
} ei[maxn];
int n, m;
struct Dinic{
struct Edge{
int v, rev, cap;
};
vector<Edge> G[maxn];
int dis[maxn], cur[maxn], n, sp, tp;
void init(int nn){
n = nn;
for(int i = 1; i <= n; ++i) G[i].clear();
}
void add(int u, int v, int cap, int rcap = 0){
G[u].pb(Edge{v, sz(G[v]), cap});
G[v].pb(Edge{u, sz(G[u]) - 1, rcap});
}
int bfs(){
queue<int> q;
for(int i = 1; i <= n; ++i) dis[i] = 0;
dis[sp] = 1, q.push(sp);
while(!q.empty()){
int u = q.front(); q.pop();
for(auto &e : G[u]){
if(e.cap && !dis[e.v]){
dis[e.v] = dis[u] + 1;
if(e.v == tp) return 1;
q.push(e.v);
}
}
}
return 0;
}
int dfs(int u, int flow){
if(u == tp || !flow) return flow;
int ret = 0, tmp;
for(int &i = cur[u]; i < sz(G[u]); ++i){
auto &e = G[u][i];
if(dis[e.v] == dis[u] + 1 && (tmp = dfs(e.v, min(e.cap, flow - ret)))){
e.cap -= tmp, G[e.v][e.rev].cap += tmp, ret += tmp;
if(ret == flow) return ret;
}
}
if(!ret) dis[u] = 0;
return ret;
}
int solve(int s, int t){
sp = s, tp = t;
int ret = 0;
while(bfs()){
for(int i = 1; i <= n; ++i) cur[i] = 0;
ret += dfs(sp, inf);
}
return ret;
}
} dn;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= m; ++i){
cin >> ei[i].u >> ei[i].v >> ei[i].w;
}
int ret = 0;
for(int i = 1; i <= m; ++i){
dn.init(n);
for(int j = 1; j <= m; ++j){
if(i == j || ei[j].w >= ei[i].w) continue;
dn.add(ei[j].u, ei[j].v, 1, 1);
}
ret += dn.solve(ei[i].u, ei[i].v);
}
cout << ret << endl;
return 0;
}
F - Philosopher’s Walk
简要题意:
给一个递归生成的网格图,问走若干步后的坐标。
解题思路:
递归生成的网格图,答案递归求解,定义 f ( n , m ) f(n, m) f(n,m) 为 n × n n×n n×n 网格上第 m m m 步的坐标,对 m m m 讨论位于四个子网格的哪个,对 f ( n / 2 , m ′ ) f(n / 2, m') f(n/2,m′) 求得答案后对坐标进行旋转翻转变换。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 5e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int pw[233];
int n, m;
pii dfs(int k, int m){
if(k == 1){
if(m == 1) return pii{1, 1};
else if(m == 2) return pii{1, 2};
else if(m == 3) return pii{2, 2};
else return pii{2, 1};
}
int d = pw[2 * (k - 1)];
pii ret;
if(m <= d){
auto tmp = dfs(k - 1, d - m + 1);
// cout << k << " " << m << " " << tmp.first << " " << tmp.second << endl;
ret = {tmp.second, pw[k - 1] - tmp.first + 1};
}
else if(m <= 2 * d){
ret = dfs(k - 1, m - d);
ret.second += pw[k - 1];
}
else if(m <= 3 * d){
ret = dfs(k - 1, m - 2 * d);
ret.first += pw[k - 1], ret.second += pw[k - 1];
}
else{
m -= 3 * d;
auto tmp = dfs(k - 1, d - m + 1);
ret = {pw[k - 1] - tmp.second + 1, tmp.first};
ret.first += pw[k - 1];
}
return ret;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
pw[0] = 1;
for(int i = 1; i <= 50; ++i) pw[i] = pw[i - 1] * 2;
cin >> n >> m;
int k;
for(int i = 0; ; ++i) if(pw[i] == n) { k = i; break; }
auto ret = dfs(k, m);
cout << ret.first << " " << ret.second << endl;
return 0;
}
G - Rectilinear Regions
简要题意:
给两条阶梯形的直线 L L L 和 U U U,问 U U U 在上方且 L L L 在下方的封闭区域个数及总面积。
解题思路:
若单调性不同则答案为 0 0 0,否则统一化为递增情况考虑。对 x x x 轴扫描处理,一个封闭区域需要满足左端点处 y u > y l y_u \gt y_l yu>yl 且右端点处 y l > y u y_l \gt y_u yl>yu,用类似状态机的写法可以较容易处理,注意左右两边的边界处理。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
struct Node{
int x, y, f;
bool operator < (const Node &o) const{
return x < o.x;
}
} a[maxn], b[maxn], c[maxn];
int n, m;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
cin >> a[0].y; a[0].x = 0, a[0].f = 0;
for(int i = 1; i <= n; ++i) cin >> a[i].x >> a[i].y, a[i].f = 0;
cin >> b[0].y; b[0].x = 0, b[0].f = 1;
for(int i = 1; i <= m; ++i) cin >> b[i].x >> b[i].y, b[i].f = 1;
int fa = a[1].y > a[0].y, fb = b[1].y > b[0].y;
if(fa ^ fb) { cout << "0 0\n"; return 0; }
if(!fa && !fb){
for(int i = 0; i <= n; ++i) a[i].y *= -1, a[i].f ^= 1;
for(int i = 0; i <= m; ++i) b[i].y *= -1, b[i].f ^= 1;
swap(n, m), swap(a, b);
}
int tot = 0;
for(int i = 1; i <= n; ++i) c[++tot] = a[i];
for(int i = 1; i <= m; ++i) c[++tot] = b[i];
sort(c + 1, c + 1 + tot);
ll ret1 = 0, ret2 = 0, tmp = 0;
int ya = a[0].y, yb = b[0].y, lst = -1, flg = ya > yb;
for(int i = 1; i <= tot; ++i){
if(!c[i].f){
if(lst != -1){
tmp += (c[i].x - lst) * 1ll * (yb - ya);
if(c[i].y > yb) lst = -1, ++ret1, ret2 += tmp, tmp = 0;
else lst = c[i].x;
}
ya = c[i].y;
flg = ya > yb;
}
else{
if(lst != -1){
tmp += (c[i].x - lst) * 1ll * (yb - ya);
lst = c[i].x;
}
yb = c[i].y;
if(lst == -1 && flg && yb > ya) lst = c[i].x;
}
}
cout << ret1 << " " << ret2 << endl;
return 0;
}
H - Rock Paper Scissors
简要题意:
给定长为 n n n 的字符串 s s s 和长为 m m m 的字符串 t t t,字符集为 R P S RPS RPS, t t t 可以与 s s s 的任一位置进行匹配,问能获得的最多的胜利次数。
解题思路:
每种胜利情况可以独立考虑,如 s i s_i si 为 R R R, t i t_i ti 为 P P P 的情况,令 a i = [ s i = R ] a_i = [s_i = R] ai=[si=R], b i = [ t i = P ] b_i = [t_i = P] bi=[ti=P],位置 i i i 进行匹配的获胜次数为 a n s i = c m − 1 + i = ∑ j = 0 m − 1 a i + j b m − 1 − j ans_i = c_{m - 1 + i} = \sum\limits_{j = 0}^{m - 1} a_{i+j}b_{m-1-j} ansi=cm−1+i=j=0∑m−1ai+jbm−1−j, F F T FFT FFT 求解。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const double pi = acos(-1.0);
struct cp{
double x, y;
cp operator + (const cp &o) const { return {x + o.x, y + o.y}; }
cp operator - (const cp &o) const { return {x - o.x, y - o.y}; }
cp operator * (const cp &o) const { return {x * o.x - y * o.y, x * o.y + y * o.x}; }
} w[maxn];
int rev[maxn];
void fft(cp x[], int len, int on){
for(int i = 0; i < len / 2; ++i){
double sita = 2 * pi * i / len;
double c = cos(sita), s = sin(sita);
w[i] = {c, s};
w[i + len / 2] = {-c, -s};
}
for(int i = 0; i < len; ++i) rev[i] = (i & 1) * (len >> 1) + (rev[i >> 1] >> 1);
for(int i = 0; i < len; ++i) if(i < rev[i]) swap(x[i], x[rev[i]]);
for(int i = 2; i <= len; i <<= 1){
int wn = len / i, d = i >> 1;
for(int j = 0; j < len; j += i){
int wk = 0;
for(int k = j; k < j + d; ++k){
cp a = x[k], b = w[wk] * x[k + d];
x[k] = a + b, x[k + d] = a - b;
wk += wn;
}
}
}
if(on == -1){
reverse(x + 1, x + len);
for(int i = 0; i < len; ++i) x[i].x /= len;
}
}
cp A[maxn], B[maxn], C[maxn];
int ans[maxn];
char s[maxn], t[maxn];
int n, m;
void solve(char ch){
int len = 1, lim = n + m;
while(len < lim) len <<= 1;
for(int i = 0; i <= len; ++i) A[i] = B[i] = {0, 0};
for(int i = 0; i < n; ++i) A[i].x = s[i] == ch ? 1 : 0;
for(int i = 0; i < m; ++i) B[i].x = t[i] == ch ? 1 : 0;
fft(A, len, 1), fft(B, len, 1);
for(int i = 0; i < len; ++i) C[i] = A[i] * B[i];
fft(C, len, -1);
for(int i = 0; i < n; ++i) ans[i] += (int)(C[m - 1 + i].x + 0.5);
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
cin >> s >> t;
for(int i = 0; i < n; ++i){
if(s[i] == 'R') s[i] = 'P';
else if(s[i] == 'S') s[i] = 'R';
else if(s[i] == 'P') s[i] = 'S';
}
reverse(t, t + m);
solve('R'), solve('S'), solve('P');
int ret = *max_element(ans, ans + n);
cout << ret << endl;
return 0;
}
I - Slot Machines
简要题意:
给一个长为 n n n 的数组 a i a_i ai,一组 ( k , d ) (k, d) (k,d) 需要满足: a [ k + 1 : n ] a[k + 1 : n] a[k+1:n] 的循环节是 d d d,问 k + d k + d k+d 的最小值,值相同则优先 d d d 小的。
解题思路:
注意 d d d 是正整数,边界情况不是 k = n , d = 0 k = n, d = 0 k=n,d=0。将数组反转,利用 k m p kmp kmp 可以求出任意前缀的最小循环节,枚举后缀长度 k k k 即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int a[maxn], nxt[maxn];
int n;
void cNext(){
int i = 0, j = -1; nxt[0] = -1;
while(i < n){
if(j == -1 || a[i] == a[j]) nxt[++i] = ++j;
else j = nxt[j];
}
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for(int i = 0; i < n; ++i) cin >> a[i];
reverse(a, a + n);
cNext();
pii ret = {0, n};
for(int i = 1; i <= n; ++i){
pii tmp = {n - i, i - nxt[i]};
if(tmp.first + tmp.second < ret.first + ret.second
|| tmp.first + tmp.second == ret.first + ret.second
&& tmp.second < ret.second) ret = tmp;
}
cout << ret.first << " " << ret.second << endl;
return 0;
}
K - Untangling Chain
简要题意:
给出一组 ( l i , e i ) (l_i, e_i) (li,ei) 表示二维平面上一个点每次前进的长度以及下次的转向,点的轨迹形成若干线段,求修改 l i l_i li 在 [ 1 , n ] [1, n] [1,n] 范围使得线段不相交。
解题思路:
螺旋走位,每次让点落在原先边界坐标之外一格。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int len[maxn], dir[maxn];
int n;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; ++i){
cin >> len[i] >> dir[i];
}
vector<int> ans;
for(int i = 1; i <= n; ++i){
if(i <= 2 || dir[i - 1] != dir[i - 2]) ans.pb(1);
else ans.pb(i);
}
for(auto &v : ans) cout << v << " "; cout << endl;
return 0;
}
L - Vacation Plans
简要题意:
给定三个有向带权图,各自有起点、终点,经过一条边为一个时间单位,求三者同时到达终点的最小总花费。
解题思路:
按时间分层后 d p dp dp 最短路,时间层数为 n 1 ∗ n 2 ∗ n 3 n_1*n_2*n_3 n1∗n2∗n3。时空比较紧,不能建完图后再跑 d p dp dp,甚至跑最短路算法,按拓扑序 d p dp dp 即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll oo = 1ll << 60;
const int lim = 125000 + 5;
struct Solution{
struct Edge{
int u, v, w;
} ei[maxn];
vector<Edge> G[maxn];
ll dis[lim][50];
int n, m, tot, tp;
void read(){
cin >> n >> m;
for(int i = 0; i < n; ++i){
int w; cin >> w;
G[i].pb(Edge{i, i, w});
}
for(int i = 1; i <= m; ++i){
int u, v, w; cin >> u >> v >> w; --u, --v;
G[u].pb(Edge{u, v, w});
}
cin >> tp; --tp;
}
void topo(){
for(int i = 0; i < lim; ++i){
for(int j = 0; j < n; ++j) dis[i][j] = oo;
}
dis[0][0] = 0;
for(int i = 0; i < lim - 1; ++i){
for(int j = 0; j < n; ++j){
for(auto &e : G[j]){
dis[i + 1][e.v] = min(dis[i + 1][e.v], dis[i][j] + e.w);
}
}
}
}
ll getDT(int d){
return dis[d][tp];
}
} g[3];
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int n; cin >> n;
for(int i = 0; i < n; ++i){
g[i].read();
g[i].topo();
}
ll ret = oo;
for(int i = 1; i < lim; ++i){
ll tmp = 0;
for(int j = 0; j < n; ++j){
tmp += g[j].getDT(i);
}
ret = min(ret, tmp);
}
cout << ret << endl;
return 0;
}