文章目录
ACM-ICPC 2018 徐州赛区网络预赛
- A. Hard to prepare
- B. BE, GE or NE
- C. Cacti Lottery
- D. Easy Math
- E. End Fantasy VIX
- F. Features Track
- G.Trace
- H. Ryuji doesn’t want to study
- I. Characters with Hash
- J. Maze Designer
- K. Morgana Net
A. Hard to prepare
题意: 有
N
N
N 个客人
2
K
2^K
2K 顶帽子, 每顶帽子编号为
0
∼
2
K
−
1
0 \sim 2^K - 1
0∼2K−1,
N
N
N 个客人围成一个圈, 要求相邻两个客人的帽子编号(K位二进制)同或不为0。测试样例T<=20, 0<N,k<1e6
题解: 分类递推
f
1
[
n
]
:
f1[n]:
f1[n]: 长度为n的序列任意两个至少有一位相同的方案数(不保证首位不相同)
f
2
[
n
]
:
f2[n]:
f2[n]: 长度为n的序列,首位完全不同的方案
a
n
s
=
f
1
[
n
]
−
f
2
[
n
]
ans = f1[n] - f2[n]
ans=f1[n]−f2[n]
转移方程:
f
1
[
n
]
=
f
1
[
n
−
1
]
∗
(
2
k
−
1
)
f1[n] = f1[n - 1] * (2 ^k - 1)
f1[n]=f1[n−1]∗(2k−1)
f
2
[
n
]
=
f
1
[
n
−
1
]
−
(
f
1
[
n
−
2
]
−
f
2
[
n
−
2
]
)
f2[n] = f1[n - 1] - (f1[n - 2] - f2[n - 2])
f2[n]=f1[n−1]−(f1[n−2]−f2[n−2]) (注解:f1[n-1]-1和n-1完全相同的方案数)
入口:
f
1
[
1
]
=
2
k
,
f
2
[
1
]
=
0
,
f
1
[
2
]
=
2
k
∗
(
2
k
−
1
)
,
f
2
[
2
]
=
0
f1[1] = 2^k, f2[1]=0, f1[2] = 2^k * (2^k - 1), f2[2] = 0
f1[1]=2k,f2[1]=0,f1[2]=2k∗(2k−1),f2[2]=0
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int const N = 1e6 + 10, mod = 1e9 + 7;
LL n, k, T, f1[N], f2[N];
map<pair<int, int>, LL> mp;
LL qmi(LL a, LL k, LL p){
LL res = 1 % p;
while(k) {
if (k & 1) res = res * a % p;
k >>= 1;
a = a * a % p;
}
return res % p;
}
int main() {
cin >> T;
while(T--) {
scanf("%lld%lld", &n, &k);
if (mp.count({n, k})) {
printf("%lld\n", mp[{n, k}]);
continue;
}
memset(f1, 0, sizeof f1);
memset(f2, 0, sizeof f2);
f1[1] = qmi(2, k, mod);
f1[2] = f1[1] * (qmi(2, k, mod) - 1) % mod;
for (int i = 3; i <= n; ++i) {
f1[i] = f1[i - 1] * (qmi(2, k, mod) - 1 + mod) % mod;
f2[i] = (((f1[i - 1] - f1[i - 2]) % mod + mod) % mod + f2[i - 2]) % mod;
}
LL res = f1[n] - f2[n];
res = (res % mod + mod) % mod;
printf("%lld\n", res);
mp[{n, k}] = res;
}
return 0;
}
B. BE, GE or NE
题意: 两个人的博弈,一开始有一个初始分数值,有三种操作,分别为+a,-b和变为相反数,先手希望走向Good Ending,后手希望走向Bad Ending,如果不能的话都希望走向Normal Ending。两人都以最优策略进行游戏。求问n轮过后结局是哪一种。
题解: 博弈论的经典题目(算是吧,做法挺经典的)
首先我们知道对于某一个状态,其必胜态条件是子状态中至少有一个其必败态,其必败态条件是子状态中全为必胜态。
对于这种最朴素的问题,我们就采用对对应状态的记忆化搜索来解决,这是一种经典解法。
对于此题来讲,我们的状态就是当前为第几回合和当前分数的组合。
我们定义先手胜利为0,后手胜利为2,平局为1.
那么对于先手,肯定是取后面的状态中返回值最小的,而对于后手,肯定是取后面的状态中返回值最大的,因为能有利于自己的话,采取最优策略一定不会选择去往平局上靠。
用一个二维数组记忆化一下就可以了。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int const N = 1e6 + 10;
int T, n, f[1010][250], a[1010], b[1010], c[1010], m, up, lower;
unordered_map<int, int> id;
int dfs(int cnt, int score) {
if (cnt == n + 1) {
if (score >= up) return 0;
else if (score <= lower ) return 2;
else return 1;
}
if (f[cnt][id[score]] != -1) return f[cnt][id[score]];
int ans;
if (cnt & 1) {
ans = 2;
if (a[cnt]) ans = min(ans, dfs(cnt + 1, min(100, score + a[cnt])));
if (b[cnt]) ans = min(ans, dfs(cnt + 1, max(-100, score - b[cnt])));
if (c[cnt]) ans = min(ans, dfs(cnt + 1, -score));
}
else {
ans = 0;
if (a[cnt]) ans = max(ans, dfs(cnt + 1, min(100, score + a[cnt])));
if (b[cnt]) ans = max(ans, dfs(cnt + 1, max(-100, score - b[cnt])));
if (c[cnt]) ans = max(ans, dfs(cnt + 1, -score));
}
return f[cnt][id[score]] = ans;
}
int main() {
cin >> n >> m >> up >> lower;
memset(f, -1, sizeof f);
for (int i =1 ; i <= n; ++i) scanf("%d%d%d", &a[i], &b[i], &c[i]);
for (int i = -100; i <= 100; ++i) {
id[i] = i + 100;
}
int ans = dfs(1 , m);
if (ans == 0) {
cout << "Good Ending\n";
}
else if (ans == 1) {
cout << "Normal Ending\n";
}
else cout << "Bad Ending\n";
return 0;
}
D. Easy Math
题意: 给定n,m,求解
∑
i
=
1
m
μ
(
i
∗
n
)
,
1
<
=
m
<
=
2
∗
1
0
9
,
1
<
=
n
<
=
1
0
12
\sum_{i=1}^{m} \mu(i * n), 1 <= m <= 2*10^9, 1 <= n <= 10^{12}
∑i=1mμ(i∗n),1<=m<=2∗109,1<=n<=1012
题解:
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int const N = 1e7 + 10;
LL n, m;
int prime[N], cnt, mu[N], st[N];
void init(int n) {
mu[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!st[i]) prime[cnt++] = i, mu[i] = -1;
for (int j = 0; prime[j] <= n / i; ++j) {
st[i * prime[j]] = 1;
if (i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
for (int i = 1; i <= n; ++i) mu[i] += mu[i - 1];
return ;
}
int get_mu(LL x) {
if (x == 1) return 1;
int num = 0;
for (int i = 2; i <= x / i; ++i) { // 因为一个数x的所有因子内只会有一个大于sqrt(x), 所以只要枚举到sqrt(x),而后单独判断大于sqrt(x)的那个即可
if (x % i == 0) { // 如果能够整除
int s = 0; // 记录能够整除几次
while (x % i == 0) {
s++;
x /= i;
if (s >= 2) return 0;
}
num++;
}
}
if (x > 1) num++;
if (num & 1) return -1;
else return 1;
}
LL S(LL m) {
if (m < N) return mu[m];
LL res = 0;
for (int l = 2, r; l <= m; l = r + 1) {
r = min(m, m / (m / l));
res += (r - l + 1) * S(m / l);
}
return 1 - res;
}
LL F(LL n, LL m) {
if (n == 1) return S(m);
LL tmp = 0;
for (int i = 1; i <= n / i && i <= m; ++i) {
if (n % i == 0) {
tmp += get_mu(i) * F(i, m / i);
if (i != n / i) tmp += get_mu(n / i) * F(n / i, m / (n / i));
}
}
return tmp * get_mu(n);
}
int main() {
init(N - 1);
scanf("%lld%lld", &m, &n);
printf("%lld\n", F(n, m));
return 0;
}
F. Features Track
题意: 给定很多个点,求能够在能够在连续的几帧出现的点最多出现在连续几帧
题解: 给每个点用一个队列存一下在哪几帧出现过,然后遍历每个队列,判断即可
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
int const N = 1e5 + 10;
int T, n, cnt;
vector<int> v[N];
map<PII, int> mp;
map<PII, int> vis;
int mapping(PII t) {
if (mp.count(t)) return mp[t];
else return mp[t] = ++cnt;
}
int get_longest(vector<int> x) {
int maxv = 1, tmp = 1;
int len = x.size();
for (int i = 0; i < len - 1; ++i) {
if (x[i + 1] == x[i] + 1) tmp ++;
else {
maxv = max(maxv, tmp);
tmp = 1;
}
}
return maxv;
}
int main() {
cin >> T;
while(T--) {
int v_cnt = 0;
mp.clear();
cnt = 0;
scanf("%d", &n);
for (int i = 1, len; i <= n; ++i) {
vis.clear();
scanf("%d", &len);
if (!len) continue;
for (int j = 1, a, b; j <= len; ++j) {
scanf("%d%d", &a, &b);
if (vis.count({a, b})) continue;
else vis[{a, b}] = 1;
v_cnt = mapping({a, b});
v[v_cnt].push_back(i);
// cout << v_cnt << endl;
}
}
// cout << cnt << endl;
int maxv = -1;
for (int i = 1; i <= cnt; ++i) {
int tmp = get_longest(v[i]);
maxv = max(maxv, tmp);
}
for (int i = 1; i <= cnt; ++i) v[i].clear();
printf("%d\n", maxv);
}
return 0;
}
G.Trace
题意: 给定许多个海浪,每次新的海浪会覆盖前一个海浪的痕迹,但是不会出现两个海浪完全覆盖的情形,求所有海浪过后海滩上留下的痕迹总长。
题解: 分析可知,前一个海浪会被后一个海浪的痕迹覆盖,最后一个海浪的痕迹会被完全留下来,因此需要倒着分析。每次到找到一个小于当前x或者y的海浪,然后x和这个海浪的差值累加到答案中.
代码:
#include <bits/stdc++.h>
typedef long long LL;
using namespace std;
int const N = 5e4 + 10;
int x[N], y[N], n;
set<int> sx, sy;
int main() {
cin >> n;
for (int i = 1, a, b; i <= n; ++i) {
scanf("%d%d", &a, &b);
x[i] = a, y[i] = b;
}
LL res = 0;
sx.insert(0), sy.insert(0);
for (int i = n; i >= 1; --i) {
auto it = sx.lower_bound(x[i]);
res += x[i] - (*(--it));
it = sy.lower_bound(y[i]);
res += y[i] - (*(--it));
sx.insert(x[i]), sy.insert(y[i]);
}
cout << res << endl;
return 0;
}
H. Ryuji doesn’t want to study
题意: 一开始给定n个数,m次询问。每次询问两种类型:
1 x y, 表示询问a[l]*len + a[l + 1] * (len - 1) + … + a[r] * 1,len = r - l + 1。
2 x y, 表示把a[x]赋值为y
n ~ 1e5, m ~ 1e5
题解:
∑
i
=
l
r
a
[
i
]
∗
(
r
−
i
+
1
)
=
(
r
+
1
)
∑
i
=
l
r
a
[
i
]
−
∑
i
=
l
r
i
∗
a
[
i
]
\sum_{i=l}^ra[i]*(r - i + 1) = (r+1)\sum_{i=l}^ra[i] - \sum_{i=l}^ri * a[i]
∑i=lra[i]∗(r−i+1)=(r+1)∑i=lra[i]−∑i=lri∗a[i]
因此,可以使用树状数组分别维护a[i]和i * a[i]的前缀和即可
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n, m;
int const N = 1e5 + 10;
LL c1[N], c2[N], a[N], sum1[N], sum2[N]; // c1[x]维护ai的前缀和,c2[x]维护i*ai的前缀和
LL lowbit(LL x) {
return x & -x;
}
// 单点修改
void add(LL c[], int x, LL y) {
for (int i = x; i <= n; i += lowbit(i)) c[i] += y; // 不断往父节点跳
}
// 查询Sx前缀和
LL query(int x, LL c[]) {
LL res = 0;
for (int i = x; i; i -= lowbit(i)) res += c[i]; // 不断往前一个兄弟节点跳
return res;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
scanf("%lld", &a[i]); // 读入a[i]
sum1[i] = sum1[i - 1] + a[i];
sum2[i] = sum2[i - 1] + i * a[i];
}
for (int i = 1; i <= n; ++i) {
c1[i] = sum1[i] - sum1[i - lowbit(i)];
c2[i] = sum2[i] - sum2[i - lowbit(i)];
}
while (m--) {
LL op, x, y;
scanf("%lld%lld%lld", &op, &x, &y);
if (op == 1) { // 询问
printf("%lld\n", (y + 1) * (query(y, c1) - query(x - 1, c1)) - (query(y, c2) - query(x - 1, c2)));
}
else { // 单点修改
add(c1, x, y - a[x]), add(c2, x, x * y - x * a[x]);
a[x] = y;
}
}
return 0;
}
I. Characters with Hash
题意: 签到
题解:
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int T, n;
int main() {
cin >> T;
while(T--) {
scanf("%d", &n);
char c;
cin >> c;
string s;
cin >> s;
int res = 0;
int j = 0;
for (int i = 0; i < s.size(); ++i) {
if (s[i] != c) break;
else j++;
}
// cout << j << endl;
if (j == s.size()) cout << 1 << endl;
else {
int tmp = abs(s[j] - c);
if (tmp < 10) res += 1;
else res += 2;
res += (s.size() - j - 1) * 2;
cout << res << endl;
}
}
return 0;
}
J. Maze Designer
题意: 给定一个n * m的方格,每个方格和下方和右方的方格有一条边,权值给定。要求将方格n * m个点连成一个最大生成树,然后给定任意两个点,求他们之间的距离。查询个数1e5
题解: 先求出最大生成树(树的权值最大,cnt-树的权值 最小),然后lca查询
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 510 * 510, M = N * 2 * 2;
int f[N][20], d[N], dist[N]; // f[i][j]表示从i开始,往上走2^j步到达的点,d为深度,dist为距离
int e[M], ne[M], h[N], idx, w[M], cnt, fa[N];
int T, n, m, Q, t; // t为数的深度
queue<int> q;
struct Edge{
int u, v, w;
bool operator<(const Edge &e)const {
return w > e.w;
}
}edge[M];
vector<int> tree_edge;
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
// 预处理:得到每个点的深度,距离,f数组
void bfs() {
q.push(1); // 把根放入队列,注意这里有可能根不是1
d[1] = 1;
while (q.size()) {
int x = q.front();
q.pop();
for (int i = h[x]; i != -1; i = ne[i]) {
int y = e[i];
if (d[y]) continue;
d[y] = d[x] + 1; // 更新深度
dist[y] = dist[x] + w[i]; // 更新距离
// 进行dp更新
f[y][0] = x;
for (int j = 1; j <= t; ++j) {
f[y][j] = f[f[y][j - 1]][j - 1]; // 分两段处理
}
q.push(y);
}
}
}
// 查找x和y的最近公共祖先
int lca(int x, int y) {
if (d[x] > d[y]) swap(x, y); // 保证x的深度浅一点
for (int i = t; i >= 0; --i)
if (d[f[y][i]] >= d[x]) y = f[y][i]; // 让x和y到同一个深度
if (x == y) return x;
for (int i = t; i >= 0; --i) {// 让x和y之差一步就能相遇
if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
}
return f[x][0];
}
int get(int x) {
if (x == fa[x]) return x;
int root = get(fa[x]);
return fa[x] = root;
}
void kurskal(){
sort(edge, edge + cnt);
for (int i = 0; i < cnt; ++i) {
int a = edge[i].u, b = edge[i].v;
a = get(a), b = get(b);
if (a != b) {
fa[a] = b;
tree_edge.push_back(i);
}
}
return;
}
int main() {
cin >> n >> m;
getchar();
memset(h, -1, sizeof h);
for (int i = 0; i < n * m; ++i) {
fa[i + 1] = i + 1;
char op1, op2;
int d, r;
scanf("%c %d %c %d", &op1, &d, &op2, &r);
getchar();
int cur = i + 1, right = cur + 1, down = cur + m;
if (op1 != 'X') edge[cnt++] = {cur, down, d};
if (op2 != 'X') edge[cnt++] = {cur, right, r};
}
kurskal();
for (int i = 0; i < tree_edge.size(); ++i) {
int a = edge[tree_edge[i]].u, b = edge[tree_edge[i]].v, c = edge[tree_edge[i]].w;
add(a, b, 1), add(b, a, 1);
}
idx = t = 0;
t = (int)(log(n * m) / log(2)) + 1; // 得到树的深度
cin >> Q;
bfs();
for (int i = 1; i <= Q; ++i) {
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
x1 --, y1--, x2--,y2--;
int a = x1 * m + y1 + 1, b = x2 * m + y2 + 1;
printf("%d\n", dist[a] + dist[b] - 2 * dist[lca(a, b)]);
}
return 0;
}
K. Morgana Net
题意: 给定
N
∗
N
N * N
N∗N的A矩阵和
(
2
m
+
1
)
∗
(
2
m
+
1
)
(2m+1)*(2m+1)
(2m+1)∗(2m+1)的B矩阵,给定
f
f
f函数,
A
′
=
f
(
A
,
B
)
A'=f(A, B)
A′=f(A,B),即新的A矩阵由老的A矩阵和B矩阵进行特殊运算后生成,给定运算关系如下:
同时,
f
(
x
)
=
x
f(x) = x%2
f(x)=x。问输入A矩阵和B矩阵,进行t次f运算后,产生的A矩阵中有多少个1?
题解: 上述f运算可以看作找出
A
i
j
A_{ij}
Aij,以其为中心,然后向上下左右找到一个长为(2m+1)的正方形,和B矩阵对应位置相乘累加之后赋值给
A
i
j
A_{ij}
Aij。按照这个计算性质,可以把A矩阵变成一个
1
∗
(
n
∗
n
)
1*(n*n)
1∗(n∗n)的一维向量,然后构造一个
(
n
∗
n
)
∗
(
n
∗
n
)
(n*n)*(n*n)
(n∗n)∗(n∗n)的辅助矩阵C,求出
C
t
C^t
Ct,再和A矩阵相乘计算1的数目即可。观察到对于A中的每一个元素,每一次卷积运算,所要求乘积的值的位置是固定的,那么我们就提前预处理出,对于A中的每一个元素求出参与该元素的卷积运算的对应元素的位置。
代码:
// 自己写的代码一直wa,放弃了,贴了份网上ac的代码
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <bitset>
using namespace std;
typedef long long ll;
const int MOD = 2;
const int MAXN = 40000;
typedef struct {
bitset<80> m[80];
int sizx,sizy;
}Matrix;
int n,m;
Matrix cb;
inline Matrix Mul(Matrix a, Matrix b)
{
Matrix c;
c.sizx=a.sizx;
c.sizy=b.sizy;
cb.sizy=b.sizx;
cb.sizx=b.sizy;
for(int i=0;i<b.sizy;i++)
{
cb.m[i].reset();
for(int j=0;j<b.sizx;j++)
{
cb.m[i][j]=b.m[j][i];
}
}
for (int i = 0; i < a.sizx; i++)
{
c.m[i].reset();
for (int j = 0; j < b.sizy; j++)
{
bitset<80> tmp=(a.m[i]&cb.m[j]);
c.m[i][j]=tmp.count()&1;
}
}
return c;
}
inline Matrix fastm(Matrix a, ll num)
{
Matrix res;
for(int i=0;i<a.sizx;i++) res.m[i].reset();
res.sizx=a.sizx;
res.sizy=a.sizy;
for(int i=0;i<a.sizx;i++)
res.m[i][i]=1;
while (num)
{
if (num & 1)
res = Mul(res, a);
num >>= 1;
a = Mul(a, a);
}
return res;
}
Matrix a,b,c;
int main()
{
int cas;
scanf("%d",&cas);
while(cas--)
{
int t;
scanf("%d%d%d",&n,&m,&t);
a.sizx=1;a.sizy=n*n;
a.m[0].reset();
for(int i=0;i<n*n;i++)
{
int tmp;
scanf("%d",&tmp);
a.m[0][i]=tmp&1;
}
for(int i=0;i<m;i++)
{
for(int j=0;j<m;j++)
{
int tmp;
scanf("%d",&tmp);
b.m[i][j]=tmp&1;
}
}
c.sizx=c.sizy=n*n;
int num=0;
int in=0;
m=m>>1;
for(int i=0;i<n*n;i++) c.m[i].reset();
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
for(int p=i-m;p<=i+m;p++)
{
for(int q=j-m;q<=j+m;q++)
{
in=p*n+q;
if(p>=0&&p<n&&q>=0&&q<n)
{
c.m[in][num]=(b.m[p-i+m][q-j+m]);
}
}
}
num++;
}
}
c=fastm(c,t);
a=Mul(a,c);
printf("%d\n",a.m[0].count());
}
}