A Winner Prediction
题意: n n n 个人互相打比赛,没有平局,胜者得一分,获胜者为胜场最多的人(允许并列)。已经进行了 m 1 m_1 m1 场比赛,已知结果,还有 m 2 m_2 m2 场比赛没打,问 1 1 1 号选手是否可能获胜。 n , m 1 , m 2 ≤ 500 n,m_1,m_2 \leq 500 n,m1,m2≤500。
解法:对于未打的 m 2 m_2 m2 场中,有 1 1 1 号选手的场次肯定让 1 1 1 号选手赢。对于还未打的场次,需要安排一个胜场方案,使得剩下的每个人的总胜场数不超过 1 1 1 号选手,即第 i i i 个人不能胜超过 a i a_i ai 场。对于这种安排类问题,可以转化为经典网络流问题:源点 S S S 向场次连流量为 1 1 1 的边,场次向参赛的二人各连一条流量为 1 1 1 的边,最后每个人(除 1 1 1)再向汇点 T T T 连流量为 a i a_i ai 的边,看最大流是否为还未决定的场次数。
#include <bits/stdc++.h>
using namespace std;
const int N = 5000, inf = 0x3f3f3f3f;
struct line
{
int from;
int to;
int v;
int next;
};
struct line que[2 * N + 5];
int headers[N + 5], depth[N + 5], cnt;
void add(int from, int to, int v)
{
que[cnt].from = from;
que[cnt].to = to;
que[cnt].v = v;
que[cnt].next = headers[from];
headers[from] = cnt;
cnt++;
}
bool bfs(int s, int t)
{
memset(depth, 0, sizeof(depth));
queue<int> q;
q.push(s);
depth[s] = 1;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = headers[u]; i != -1; i = que[i].next)
if (!depth[que[i].to] && que[i].v > 0)
{
depth[que[i].to] = depth[u] + 1;
q.push(que[i].to);
}
}
return depth[t] != 0;
}
int dfs(int u, int t, int flow)
{
if (u == t || flow <= 0)
return flow;
for (int i = headers[u]; i != -1; i = que[i].next)
if (depth[que[i].to] == depth[u] + 1)
{
int d = dfs(que[i].to, t, min(flow, que[i].v));
if (d > 0)
{
que[i].v -= d;
que[i ^ 1].v += d;
return d;
}
}
depth[u] = -1;
return 0;
}
int dinic(int s, int t)
{
int ans = 0;
while (bfs(s, t))
while (int d = dfs(s, t, inf))
ans += d;
return ans;
}
int score[N + 5];
int main()
{
int t, n, m1, m2;
scanf("%d", &t);
while (t--)
{
scanf("%d%d%d", &n, &m1, &m2);
for (int i = 1; i <= n;i++)
score[i] = 0;
cnt = 0;
int s = 0, t = n + m2 + 1;
for (int i = s; i <= t;i++)
headers[i] = -1;
for (int i = 1, u, v, w; i <= m1;i++)
{
scanf("%d%d%d", &u, &v, &w);
if(w)
score[u]++;
else
score[v]++;
}
int ava = 0;
for (int i = 1, u, v; i <= m2;i++)
{
scanf("%d%d", &u, &v);
if (u == 1 || v == 1)
{
score[1]++;
continue;
}
else
{
ava++;
add(s, n + i, 1);
add(n + i, s, 0);
add(n + i, u, 1);
add(u, n + i, 0);
add(n + i, v, 1);
add(v, n + i, 0);
}
}
bool flag = 1;
for (int i = 2; i <= n;i++)
{
if(score[i] > score[1])
{
flag = 0;
break;
}
add(i, t, score[1] - score[i]);
add(t, i, 0);
}
if (!flag)
{
printf("NO\n");
continue;
}
if(dinic(s, t) == ava)
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
B Photos
题意: n × n n \times n n×n 的棋盘,有 m m m 个特殊格点。问有多少种覆盖矩形的方式,使得每个矩形的左上角和右下角均在主对角线上,且不同矩形之间没有交,并且每个特殊格点都被恰好一个矩形覆盖。 n ≤ 1 × 1 0 9 n \leq 1\times 10^9 n≤1×109, m ≤ 1 × 1 0 5 m \leq 1\times 10^5 m≤1×105。
解法:每个特殊格点 ( x , y ) (x,y) (x,y) 都会对应于主对角线上一段区间 [ l , r ] [l,r] [l,r]: l = min ( x , y ) , r = max ( x , y ) l=\min(x,y),r=\max(x,y) l=min(x,y),r=max(x,y),要求 [ l + 1 , r − 1 ] [l+1,r-1] [l+1,r−1] 禁止有矩形的顶点,并且这一段必须有矩形包含。
首先将每个特殊格点对应于主对角线上范围进行排序和合并得到一些不交的区间,问题转化为区间覆盖问题:在长度为 n n n 的数轴上选取若干个不相交的区间,使得它们包含所有的特殊区间。注意到特殊区间中间内部不允许选分隔点(即对于特殊区间 [ l , r ] [l,r] [l,r],不允许选出 [ l , x ] , [ x , r ] [l,x],[x,r] [l,x],[x,r] 进行覆盖,其中 x ∈ [ l , r ] x \in[l,r] x∈[l,r]),因而可以将每个特殊区间 [ l , r ] [l,r] [l,r] 合并成一个点。这是因为一个区间若包含 [ l , r − 1 ] [l,r-1] [l,r−1] 内部的任何点,当前区间必然要包含这内部的所有点。
考虑朴素的 dp:
f
i
f_i
fi 表示数轴上覆盖前
i
i
i 个点中全部特殊区间的方案数,初值
f
0
=
1
f_0=1
f0=1。如果当前点不是特殊区间的结束点
i
i
i,即当前点不是必选,则转移非常显然:
f
i
←
f
i
−
1
+
∑
j
=
1
i
−
1
f
j
\displaystyle f_i \leftarrow f_{i-1}+\sum_{j=1}^{i-1}f_j
fi←fi−1+j=1∑i−1fj,即第
i
i
i 个点无区间覆盖,和选择区间
[
j
+
1
,
i
]
[j+1,i]
[j+1,i] 进行覆盖两种选择。记
g
i
=
∑
j
=
1
i
f
i
\displaystyle g_i=\sum_{j=1}^i f_i
gi=j=1∑ifi 则可以利用矩阵进行转移:
[
f
i
g
i
]
=
[
f
i
−
1
g
i
−
1
]
[
1
1
1
2
]
\begin{bmatrix} f_i&g_i\\ \end{bmatrix}= \begin{bmatrix} f_{i-1}&g_{i-1}\\ \end{bmatrix} \begin{bmatrix} 1&1\\ 1&2\\ \end{bmatrix}
[figi]=[fi−1gi−1][1112]
对于特殊区间的覆盖点
i
i
i,则
f
i
←
∑
j
=
1
i
−
1
f
j
\displaystyle f_i \leftarrow \sum_{j=1}^{i-1}f_j
fi←j=1∑i−1fj,因为只有一种选择:选择
[
j
,
i
]
[j,i]
[j,i] 覆盖。同样可以写成矩阵形式:
[
f
i
g
i
]
=
[
f
i
−
1
g
i
−
1
]
[
0
0
1
2
]
\begin{bmatrix} f_i&g_i \end{bmatrix}= \begin{bmatrix} f_{i-1}&g_{i-1} \end{bmatrix} \begin{bmatrix} 0&0\\ 1&2 \end{bmatrix}
[figi]=[fi−1gi−1][0102]
设
A
=
[
1
1
1
2
]
,
B
=
[
0
0
1
2
]
A=\begin{bmatrix}1&1\\1&2\end{bmatrix},B=\begin{bmatrix}0&0\\1&2\end{bmatrix}
A=[1112],B=[0102]。注意到只有
m
m
m 个特殊格点,因而整个数轴区间只会被这
m
m
m 个格点分成
O
(
m
)
O(m)
O(m) 段,每一段都是由强制选择格点+无限制转移组成的。因而若两个特殊区间相距为
l
l
l,则转移矩阵为
B
A
l
−
1
BA^{l-1}
BAl−1,可以
O
(
log
n
)
\mathcal O(\log n)
O(logn) 的计算一整段。注意对于第一段转移是没有
B
B
B 矩阵的,因为最开头不涉及受限制的转移。因而总时间复杂度为
O
(
k
3
m
log
n
)
\mathcal O(k^3m \log n)
O(k3mlogn),其中
k
=
2
k=2
k=2 为矩阵大小。
#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353;
struct matrix
{
int a[2][2];
matrix()
{
memset(a, 0, sizeof(a));
}
};
matrix operator*(matrix a, matrix b)
{
matrix ans;
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
for (int k = 0; k < 2; k++)
ans.a[i][j] = (ans.a[i][j] + 1ll * a.a[i][k] * b.a[k][j] % mod) % mod;
return ans;
}
matrix power(matrix a, long long x)
{
matrix ans;
ans.a[0][0] = ans.a[1][1] = 1;
while (x)
{
if (x & 1)
ans = ans * a;
a = a * a;
x >>= 1;
}
return ans;
}
vector<pair<int, int>> merge(vector<pair<int, int>> a)
{
vector<pair<int, int>> ans;
int l = -1, r = 0;
for (auto i : a)
{
if (i.first > r)
{
if (~l)
ans.emplace_back(l, r);
l = i.first;
r = i.second;
}
else
r = max(r, i.second);
}
if (~l)
ans.emplace_back(l, r);
return ans;
}
int main()
{
int t, n, m;
matrix all, part;
all.a[0][0] = all.a[0][1] = all.a[1][0] = 1;
all.a[1][1] = 2;
part.a[0][0] = part.a[0][1] = 0;
part.a[1][0] = 1;
part.a[1][1] = 2;
scanf("%d", &t);
while (t--)
{
scanf("%d%d", &n, &m);
vector<pair<int, int>> ban;
for (int i = 1, x, y; i <= m; i++)
{
scanf("%d%d", &x, &y);
if (x > y)
swap(x, y);
ban.emplace_back(x, y - 1);
}
sort(ban.begin(), ban.end());
ban = merge(ban);
ban.emplace_back(n + 1, n + 1);
int last = 0;
matrix ans;
ans.a[0][0] = ans.a[1][1] = 1;
for (auto i : ban)
{
int len = i.first - last - 1;
if (last && len)
ans = ans * part * power(all, len - 1);
else if (len)
ans = ans * power(all, len);
last = i.second;
}
printf("%d\n", (ans.a[0][0] + ans.a[1][0]) % mod);
}
return 0;
}
C Wavy Tree
题意:给定长度为 n n n 的数列 { a i } \{a_i\} {ai}, a i ← a i + 1 a_i \leftarrow a_i+1 ai←ai+1 或 a i ← a i − 1 a_i \leftarrow a_i-1 ai←ai−1 代价均为 1 1 1,问将数列 { a i } \{a_i\} {ai} 变成波浪数组(即 a 1 < a 2 > a 3 < ⋯ < a n − 1 > a n a_1 <a_2>a_3< \cdots<a_{n-1}>a_n a1<a2>a3<⋯<an−1>an 或 a 1 > a 2 < a 3 < ⋯ > a n − 1 < a n a_1 >a_2< a_3<\cdots >a_{n-1}<a_n a1>a2<a3<⋯>an−1<an)的最小代价。 n ≤ 1 × 1 0 6 n \leq 1\times 10^6 n≤1×106, a i ≤ 1 × 1 0 9 a_i \leq 1\times 10^9 ai≤1×109。
解法:考虑 { a i } \{a_i\} {ai} 的差分数组 d i = a i − a i − 1 d_i=a_i-a_{i-1} di=ai−ai−1,则必然为一正一负交替出现,且一次操作只会影响 d i d_i di 和 d i + 1 d_{i+1} di+1:当 d i ← d i + x d_i \leftarrow d_i+x di←di+x 时, d i + 1 ← d i + 1 − x d_{i+1} \leftarrow d_{i+1}-x di+1←di+1−x。因而对于两种情况,从左到右的贪心即可,不满足条件的差分尽可能的修改到 ± 1 \pm 1 ±1。整体时间复杂度 O ( n ) \mathcal O(n) O(n)。
#include <bits/stdc++.h>
using namespace std;
const int N = 1000000;
long long a[N + 5], d[N + 5];
long long odd(int n)
{
long long ans = 0;
for (int i = 1; i <= n;i++)
d[i] = a[i] - a[i - 1];
for (int i = 2; i <= n;i++)
if (i % 2 == 1)
{
if (d[i] >= 0)
{
ans += d[i] + 1;
d[i + 1] += d[i] + 1;
}
}
else
{
if (d[i] <= 0)
{
ans += -d[i] + 1;
d[i + 1] -= -d[i] + 1;
}
}
return ans;
}
long long even(int n)
{
long long ans = 0;
for (int i = 1; i <= n;i++)
d[i] = a[i] - a[i - 1];
for (int i = 2; i <= n;i++)
if (i % 2 == 0)
{
if (d[i] >= 0)
{
ans += d[i] + 1;
d[i + 1] += d[i] + 1;
}
}
else
{
if (d[i] <= 0)
{
ans += -d[i] + 1;
d[i + 1] -= -d[i] + 1;
}
}
return ans;
}
int main()
{
int t, n;
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
for (int i = 1; i <= n;i++)
scanf("%lld", &a[i]);
printf("%lld\n", min(odd(n), even(n)));
}
return 0;
}
D Average Replacement
题意:给定 n n n 个点 m m m 条边的无向图 G ( V , E ) G(V,E) G(V,E),每个点上有点权 a i a_i ai。一次操作后对于第 i i i 个点,其点权变为 a i + ∑ ( i , j ) ∈ E a j d e g i + 1 \dfrac{a_i+\sum_{(i,j) \in E} a_j}{{\rm deg}_i+1} degi+1ai+∑(i,j)∈Eaj,问经过无穷次操作后,每个点点权收敛多少。 n , m ≤ 2 × 1 0 5 n,m \leq 2\times 10^5 n,m≤2×105。
解法:
引理 1:对于一个连通图 G G G,一次操作后 ∑ u ∈ V a u ( d e g u + 1 ) \displaystyle \sum_{u \in V}a_u({\rm deg}_u+1) u∈V∑au(degu+1) 不变。
证明:考虑一次操作后, ∑ u ∈ V a u ( d e g u + 1 ) \displaystyle \sum_{u \in V}a_u({\rm deg}_u+1) u∈V∑au(degu+1) 的变化。对于点 i i i,若之前的权值为 a i a_i ai,之后的权值为 a i ′ a_i' ai′,则它会对外产生 a i + d e g i a i a_i+{\rm deg}_ia_i ai+degiai 的权值,而同时它会收到来自邻接点的权值 a i ′ + d e g i a i ′ a'_i+{\rm deg}_ia'_i ai′+degiai′,产生和收到的权值相等,因而不变。
引理 2:对于一个连通图 G G G,经过无穷多次操作后,必然每个点点权趋近于同一个值。
证明:若最终存在两个相邻的点点权不同,不妨设为 i , j i,j i,j,则对 i i i 考虑收敛条件: a i = 1 d e g i + 1 ( a i + ∑ ( i , k ) ∈ E a k ) \displaystyle a_i= \dfrac{1}{{\rm deg}_i+1}\left(a_i+\sum_{(i,k) \in E}a_k\right) ai=degi+11⎝ ⎛ai+(i,k)∈E∑ak⎠ ⎞,即 a i d e g i = ∑ ( i , k ) ∈ E a k \displaystyle a_i{\rm deg}_i=\sum_{(i,k) \in E}a_k aidegi=(i,k)∈E∑ak。为了达成这一条件,由于有 a j > a i a_j >a_i aj>ai,由平均数的性质,必然还存在一个与 i i i 邻接的 l l l 满足 a l < a i a_l<a_i al<ai。而对于 j j j 来说,由于 a i < a j a_i<a_j ai<aj,因而 j j j 的周围也一定有一个比 a j a_j aj 大的邻接点。依次这样考虑下去,必然存在一个点周围无邻接的更大权值的点,而这个点的点权经过一次操作后必然会减小,无法满足收敛条件,因而矛盾。
依据以上的引理,对于一个连通块 G G G,每个点权值收敛于 ∑ u ∈ V a u ( d e g u + 1 ) ∑ u ∈ v d e g u \dfrac{\sum_{u \in V}a_u({\rm deg}_u+1)}{\sum_{u \in v}{\rm deg}_u} ∑u∈vdegu∑u∈Vau(degu+1)。复杂度仅为图遍历的复杂度。
#include <bits/stdc++.h>
using namespace std;
const int N = 100000;
struct line
{
int from;
int to;
int next;
};
struct line que[2 * N + 5];
int cnt, deg[N + 5], headers[N + 5], vis[N + 5];
long long a[N + 5];
double ans[N + 5];
void add(int from, int to)
{
cnt++;
que[cnt].from = from;
que[cnt].to = to;
que[cnt].next = headers[from];
headers[from] = cnt;
}
int main()
{
int n, m, t;
scanf("%d", &t);
while (t--)
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n;i++)
scanf("%lld", &a[i]);
for (int i = 1, u, v; i <= m;i++)
{
scanf("%d%d", &u, &v);
add(u, v);
deg[u]++;
deg[v]++;
add(v, u);
}
for (int i = 1; i <= n; i++)
if (!vis[i])
{
queue<int> q;
vector<int> now;
long long sumdeg = 0, sum = 0;
q.push(i);
vis[i] = 1;
while (!q.empty())
{
int tp = q.front();
q.pop();
now.push_back(tp);
sumdeg += deg[tp] + 1;
sum += (deg[tp] + 1) * a[tp];
for (int j = headers[tp]; j; j = que[j].next)
if (!vis[que[j].to])
{
vis[que[j].to] = 1;
q.push(que[j].to);
}
}
double nowans = (double)sum / sumdeg;
for (auto j : now)
ans[j] = nowans;
}
for (int i = 1; i <= n;i++)
printf("%.6lf\n", ans[i]);
cnt = 0;
for (int i = 1; i <= n;i++)
headers[i] = vis[i] = deg[i] = 0;
}
return 0;
}
E Apples
题意: n n n 个人排成一个环形,相邻的人可以传递苹果。第 i i i 个人现在有 b i b_i bi 个苹果,需要 e i e_i ei 个苹果,他向第 i + 1 i+1 i+1 个人传递苹果的代价为 l i l_i li,反向代价相同。 q q q 次修改,每次将第 x x x 个人的传递代价修改为 y y y,修改有后效性,问使得每个人手上的苹果数都为需求的数目的最小代价。 ∑ b i = ∑ e i \sum b_i=\sum e_i ∑bi=∑ei, l i , y ≤ 1 × 1 0 4 l_i,y \leq 1\times 10^4 li,y≤1×104, n , q ≤ 5 × 1 0 5 n,q \leq 5\times 10^5 n,q≤5×105。
解法:记
a
i
=
∑
j
=
1
i
b
i
−
e
i
\displaystyle a_i=\sum_{j=1}^ib_i-e_i
ai=j=1∑ibi−ei。考虑枚举第一个人向第
n
n
n 个人传递了
x
x
x 个苹果(
x
x
x 可为负数,表示从第
n
n
n 个人向第一个人传递苹果),则第一个人要给第二个人
a
1
−
x
a_1-x
a1−x 个苹果(第一个人净持有
a
1
−
x
a_1-x
a1−x 个苹果,他需要把这些苹果移出去使自己清零),代价为
l
1
∣
x
−
a
1
∣
l_1|x-a_1|
l1∣x−a1∣,第二个人要给第三个人
∣
a
2
−
x
∣
|a_2-x|
∣a2−x∣ 个苹果(将第一个人和第二个人合并起来作为一个整体,则刚刚的第一个人和第二个人之间的传递就可以不考虑了,他们净持有苹果
a
2
−
x
a_2-x
a2−x 个,为了使得这个整体净含量为
0
0
0 所以第二个人要向第三个人传递
a
2
−
x
a_2-x
a2−x 个苹果),代价为
l
2
∣
x
−
a
2
∣
l_2|x-a_2|
l2∣x−a2∣……
因而,总代价为
∑
i
=
1
n
l
i
∣
x
−
a
i
∣
\displaystyle \sum_{i=1}^nl_i|x-a_i|
i=1∑nli∣x−ai∣。该式在
x
x
x 取
a
i
a_i
ai 按
l
i
l_i
li 加权的中位数时取得最小值(即
a
i
a_i
ai 有
l
i
l_i
li 个,总序列长度为
∑
l
i
\sum l_i
∑li 时的中位数)。因而首先对
a
i
a_i
ai 离散化后,利用权值线段树维护权值为
a
i
a_i
ai 时有多少个(
∑
l
i
\sum l_i
∑li),以及
∑
a
i
l
i
\sum a_il_i
∑aili 即可。总时间复杂度
O
(
q
log
n
)
\mathcal O(q \log n)
O(qlogn)。
#include <bits/stdc++.h>
using namespace std;
const int N = 500000;
long long l[N + 5], num[N + 5], a[N + 5];
int pos[N + 5];
class segment_tree
{
struct node
{
long long suml;
long long sum;
node()
{
suml = sum = 0;
}
};
vector<node> t;
int n;
void update(int place, int left, int right, int start, long long l, long long x)
{
if (left == right)
{
t[place].suml += l;
t[place].sum += x;
return;
}
int mid = (left + right) >> 1;
if (start <= mid)
update(place << 1, left, mid, start, l, x);
else
update(place << 1 | 1, mid + 1, right, start, l, x);
t[place].suml = t[place << 1].suml + t[place << 1 | 1].suml;
t[place].sum = t[place << 1].sum + t[place << 1 | 1].sum;
}
int query_pos(int place, int left, int right, long long lim)
{
if (left == right)
return left;
int mid = (left + right) >> 1;
if(t[place << 1].suml >= lim)
return query_pos(place << 1, left, mid, lim);
else
return query_pos(place << 1 | 1, mid + 1, right, lim - t[place << 1].suml);
}
pair<long long, long long> query(int place, int left, int right, int start, int end)
{
if (start <= left && right <= end)
return make_pair(t[place].suml, t[place].sum);
pair<long long, long long> ans = make_pair(0, 0);
int mid = (left + right) >> 1;
if (start <= mid)
{
auto d = query(place << 1, left, mid, start, end);
ans.first += d.first;
ans.second += d.second;
}
if (end > mid)
{
auto d = query(place << 1 | 1, mid + 1, right, start, end);
ans.first += d.first;
ans.second += d.second;
}
return ans;
}
public:
segment_tree(int n)
{
this->n = n;
t.resize(4 * n + 5);
}
void update(int pos, long long l, long long x)
{
update(1, 1, n, pos, l, x);
}
long long query()
{
int pos = query_pos(1, 1, n, (t[1].suml + 1) >> 1);
auto left = query(1, 1, n, 1, pos);
auto right = make_pair(t[1].suml - left.first, t[1].sum - left.second);
long long ans = left.first * num[pos] - left.second + right.second - right.first * num[pos];
return ans;
}
};
int main()
{
int caset, n, q, x, y;
long long b, e;
scanf("%d", &caset);
while(caset--)
{
scanf("%d", &n);
int tot = 0;
for (int i = 1; i <= n;i++)
{
scanf("%lld%lld%lld", &b, &e, &l[i]);
a[i] = b - e;
}
a[1] = 0;
num[++tot] = 0;
for (int i = 2; i <= n;i++)
{
a[i] += a[i - 1];
num[++tot] = a[i];
}
sort(num + 1, num + tot + 1);
int m = unique(num + 1, num + tot + 1) - num - 1;
segment_tree t(m);
for (int i = 1; i <= n;i++)
pos[i] = lower_bound(num + 1, num + m + 1, a[i]) - num;
for (int i = 1; i <= n;i++)
t.update(pos[i], l[i], l[i] * num[pos[i]]);
printf("%lld\n", t.query());
scanf("%d", &q);
while(q--)
{
scanf("%d%d", &x, &y);
t.update(pos[x], y - l[x], (y - l[x]) * num[pos[x]]);
l[x] = y;
printf("%lld\n", t.query());
}
}
return 0;
}
G Even Tree Split
题意:给定 n n n 个点的树(保证 n n n 为偶数),求有多少种方案将树剖分成若干连通块(至少两块),每个连通块大小都是偶数。 n ≤ 1 × 1 0 5 n \leq 1\times 10^5 n≤1×105。
解法:以 1 1 1 为根,则点 i i i 到它父亲的边能剖分当且仅当 s i z e i {\rm size}_i sizei 为偶数。因而统计真子树个数 m m m,输出 2 m − 1 2^m-1 2m−1 即可(至少剖分一次)。
I Painting Game
题意:Alice 和 Bob 在一个长为 n n n 的纸条上涂色,初始纸条全为白格,每次轮流将一个白格子涂成黑色,使得任意两个黑格不相邻,无法操作时游戏停止,统计黑格数目。Alice 希望尽可能少黑格,Bob 希望尽可能多。给定 n n n 和先后手,问最终涂色格子数。 n ≤ 1 × 1 0 9 n \leq 1\times 10^9 n≤1×109, T T T 测, T ≤ 1 × 1 0 5 T \leq 1\times 10^5 T≤1×105。
解法:小于等于 14 14 14 格暴力搜索。对于大于 7 7 7 格的方案,考虑前 7 7 7 格的涂色方案,若 Alice 先手,则必然会占据第二格,以此破坏掉第一格的涂色;而 Bob 则会选择第三格,这样可以利用第一格(边缘格)。因而若先手为 Alice,则会占据 2 , 6 , 4 2,6,4 2,6,4 三格;而 Bob 先手则会占据 3 , 5 , 1 3,5,1 3,5,1 三格,均为 7 7 7 格涂黑 3 3 3 格,以 7 7 7 为周期。因而可以化归到小于等于 14 14 14 的方案。
#include <bits/stdc++.h>
using namespace std;
int f[25], g[25];
char s[20];
int main()
{
int n, t;
scanf("%d", &t);
f[1] = f[2] = f[3] = 1;
f[4] = 2;
g[1] = g[2] = 1;
g[3] = g[4] = 2;
for (int i = 5; i <= 20; i++)
f[i] = g[i - 3] + 1, g[i] = f[i - 4] + 2;
while (t--)
{
scanf("%d %s", &n, s + 1);
if (s[1] == 'A')
{
if (n <= 20)
printf("%d\n", f[n]);
else
{
int t = n / 7 - 1;
printf("%d\n", 3 * t + f[n - t * 7]);
}
}
else
{
if (n <= 20)
printf("%d\n", g[n]);
else
{
int t = n / 7 - 1;
printf("%d\n", 3 * t + g[n - t * 7]);
}
}
}
return 0;
}