A - 走格子
思路:
直接按题意模拟即可。
#include<bits/stdc++.h>
typedef long long ll;
const int INF = 1e9 + 10;
const int maxn = 1e5 + 10;
using namespace std;
int n, m, T, kase = 1;
int a[maxn];
int dx[] = {1, 0, -1, 0};
int dy[] = {0, -1, 0, 1};
int vis[1010][1010];
int main() {
while(scanf("%d %d", &n, &m) != EOF) {
int x = 1, y = 1, st = 0;
memset(vis, 0, sizeof vis);
vis[1][1] = 1;
while(m--) {
while(1) {
int nx = x + dx[st];
int ny = y + dy[st];
if(nx < 1 || nx > n || ny < 1 || ny > n || vis[nx][ny]) {
st = (st + 1) % 4;
} else break;
}
x = x + dx[st];
y = y + dy[st];
vis[x][y] = 1;
}
printf("%d %d\n", x, y);
}
return 0;
}
B - 求值
思路:
有个公式
∑k(nk)(mx−k)=(n+mx)
∑
k
(
n
k
)
(
m
x
−
k
)
=
(
n
+
m
x
)
,然后
(nk)=(nn−k)
(
n
k
)
=
(
n
n
−
k
)
,所以
∑v(iv)2=∑v(iv)(ii−v)①=(2ii)
∑
v
(
i
v
)
2
=
∑
v
(
i
v
)
(
i
i
−
v
)
①
=
(
2
i
i
)
,然后预处理一下阶乘还有逆元就好了。我得方法是①式是卷积, 用了
NTT
N
T
T
。。。
#include<bits/stdc++.h>
typedef long long ll;
const int mod = 998244353;
const int maxn = 2e6 + 1e5;
using namespace std;
int fac, inv;
int a[maxn], b[maxn], rev[maxn];
int qmod(int x, int n, int mod) {
int ans = 1;
while(n) {
if(n & 1) ans = (ll)ans * x % mod;
x = (ll)x * x % mod;
n >>= 1;
}
return ans;
}
void NTT(int *a, int len, int op) {
for(int i = 0; i < len; i++) if(i < rev[i]) swap(a[i], a[rev[i]]);
for(int i = 1; i < len; i <<= 1) {
int wn = qmod(3, ((mod - 1) / i / 2 * op + mod - 1) % (mod - 1), mod);
///Complex wn(cos(pi / i), op * sin(pi / i));
int step = i << 1;
for(int j = 0; j < len; j += step) {
int w = 1, x, y;
for(int k = 0; k < i; k++, w = ((ll)w * wn) % mod) {
x = a[j + k]; y = ((ll)w * a[j + k + i]) % mod;
a[j + k] = (x + y) % mod; a[j + k + i] = ((x - y) % mod + mod) % mod;
}
}
}
}
void init() {
fac = inv = 1;
a[0] = b[0] = 1;
for(int i = 1; i < 1e6 + 2; i++) {
fac = (ll)fac * i % mod;
fac = (ll)fac * i % mod;
//if(i < 10) cout << "i = " << i << " fac = " << fac << endl;
inv = qmod(fac, mod - 2, mod);
a[i] = b[i] = inv;
}
// cout << (ll)(qmod(3, mod-2,mod) + 1) * (36) % mod << endl;
int len = 1, l = 0, L = 1e6 + 3;
while(len <= L + L) { len <<= 1; l++; }
//cout << len << endl;
for(int i = 0; i < len; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
NTT(a, len, 1);
for(int i = 0; i < len; i++) a[i] = (ll)a[i] * a[i] % mod;
NTT(a, len, -1); inv = qmod(len, mod - 2, mod);
for(int i = 0; i < 1000010; i++) a[i] = (ll)a[i] * inv % mod;
// cout << a[3] << endl;
rev[0] = 0; fac = 1;
for(int i = 1; i < 1000010; i++) {
fac = (ll)fac * i % mod;
fac = (ll)fac * i % mod;
rev[i] = (rev[i - 1] + (ll)fac * a[i]) % mod;
// if(i < 10) cout << "fac = " << fac << " rev" << rev[i] << endl;
}
}
int main() {
init();
int n; while(scanf("%d", &n) != EOF) printf("%d\n", rev[n]);
return 0;
}
C - 简单环
思路:
设
dp[S][i]
d
p
[
S
]
[
i
]
:现在的一条链的集合是
S
S
这个状态且结束点是开始点是
S
S
中标号最小的点的方案数,那么对于下一条链,如果
i,j(j>a)
i
,
j
(
j
>
a
)
有边且
j
j
没有在中,那么
dp[S|1<<j][j] += dp[S][i]
d
p
[
S
|
1
<<
j
]
[
j
]
+
=
d
p
[
S
]
[
i
]
,那么如果
a,k
a
,
k
也有边那就形成了一个环,从小到大枚举状态即可,每个环会重复计算两次,答案除以2就行了。
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 1e5 + 10;
const ll mod = 998244353;
using namespace std;
int vis[25][25];
ll dp[1 << 20][20], tot[1 << 20], ans[20];
int main() {
ios::sync_with_stdio(0);
int n, m, k;
ll inv = (mod + 1) / 2;
while(cin >> n >> m >> k) {
memset(vis, 0, sizeof vis);
memset(dp, 0, sizeof dp);
memset(tot, 0, sizeof tot);
memset(ans, 0, sizeof ans);
for(int i = 0; i < m; i++) {
int x, y; cin >> x >> y;
x--; y--;
vis[x][y] = vis[y][x] = 1;
}
for(int i = 0; i < n; i++) dp[1 << i][i] = 1;
int max_sta = 1 << n;
for(int i = 0; i < max_sta; i++) {
int min_point = -1;
for(int j = 0; j < n; j++) if(((i >> j) & 1)) {
if(min_point == -1) min_point = j; ///固定以标号最小的点为链的开始点
for(int k = min_point + 1; k < n; k++) {
if((i >> k) & 1) continue;
if(!vis[j][k]) continue;
int next_sta = i | (1 << k);
(dp[next_sta][k] += dp[i][j]) %= mod;
if(vis[min_point][k]) (tot[next_sta] += dp[i][j]) %= mod;
}
}
int tx = __builtin_popcount(i);
if(tx >= 3) (ans[tx % k] += tot[i] * inv) %= mod; ///环计算了两次
}
for(int i = 0; i < k; i++) cout << ans[i] << endl;
}
return 0;
}
D - 01序列
思路:
对于一个只有
01
01
的区间,设长度为
l
l
,其值表示为,如果要模
3
3
为,则有
(2l−1⋅al−1+2l−2⋅al−2+..+20⋅a0) mod 3=0=((−1)l−1⋅al−1+(−1)l−2⋅al−2+..+(−1)0⋅a0)
(
2
l
−
1
⋅
a
l
−
1
+
2
l
−
2
⋅
a
l
−
2
+
.
.
+
2
0
⋅
a
0
)
m
o
d
3
=
0
=
(
(
−
1
)
l
−
1
⋅
a
l
−
1
+
(
−
1
)
l
−
2
⋅
a
l
−
2
+
.
.
+
(
−
1
)
0
⋅
a
0
)
,当
ai
a
i
等于
0
0
时没有影响,所以只要保证这个区间在奇数位置上的和偶数位置上的
1
1
相差为的倍数即模
3
3
为,那么这个值就是
3
3
的倍数,线段树维护每个区间的倍数的区间数量
sum
s
u
m
,节点对应区间以左端点开始和以右端点开始相差值
d mod 3=0,1,2
d
m
o
d
3
=
0
,
1
,
2
的区间数量
d1,d2
d
1
,
d
2
,还有该区间的
d0
d
0
值,合并的时候
sum=suml+sumr+跨越mid的区间数量
s
u
m
=
s
u
m
l
+
s
u
m
r
+
跨
越
m
i
d
的
区
间
数
量
,跨越
mid
m
i
d
的是左孩子余数
x
x
的右孩子余数
y
y
的(
x+y mod 3=0
x
+
y
m
o
d
3
=
0
),
d0
d
0
直接相加即可,
d1,d2
d
1
,
d
2
根据两个孩子的
d0
d
0
值再加上去就行。查询的时候类似分治,求完全包含在
[l,mid],[mid+1,r]
[
l
,
m
i
d
]
,
[
m
i
d
+
1
,
r
]
的,递归解决就行,还有跨越
mid
m
i
d
的,也是类似上面的去维护。
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 5e5 + 10;
using namespace std;
struct seg {
ll l[3], r[3], sum, rec;
} C[maxn * 4];
int n, m, op, x, y;
int bit[maxn];
void push_up(int o) {
int o1 = o << 1, o2 = o << 1 | 1;
for(int i = 0; i < 3; i++) {
C[o].l[i] = C[o1].l[i];
C[o].r[i] = C[o2].r[i];
int ls = (i - C[o1].rec), rs = i - C[o2].rec;
if(ls < 0) ls += 3;
if(rs < 0) rs += 3;
C[o].l[i] += C[o2].l[ls];
C[o].r[i] += C[o1].r[rs];
}
C[o].sum = C[o1].sum + C[o2].sum;
for(int i = 0; i < 3; i++) {
C[o].sum += C[o1].r[i] * C[o2].l[(3 - i) % 3];
}
C[o].rec = (C[o1].rec + C[o2].rec) % 3;
}
void build(int o, int l, int r) {
for(int i = 0; i < 3; i++) {
C[o].l[i] = C[o].r[i] = C[o].sum = C[o].rec = 0;
}
if(l == r) {
for(int i = 0; i < 3; i++) C[o].l[i] = C[o].r[i] = C[o].sum = 0;
int ds = 0;
if(bit[l] == 0) ds = 0;
else if(l & 1) ds = 1;
else ds = 2;
C[o].l[ds] = C[o].r[ds] = 1;
C[o].rec = ds;
if(ds == 0) C[o].sum = 1;
else C[o].sum = 0;
return ;
}
int mid = (l + r) >> 1;
build(o << 1, l, mid);
build(o << 1 | 1, mid + 1, r);
push_up(o);
}
void update(int o, int l, int r, int ind) {
if(l == r) {
for(int i = 0; i < 3; i++) C[o].l[i] = C[o].r[i] = C[o].sum = 0;
bit[l] ^= 1;
int ds = 0;
if(bit[l] == 0) ds = 0;
else if(l & 1) ds = 1;
else ds = 2;
C[o].l[ds] = C[o].r[ds] = 1;
if(ds == 0) C[o].sum = 1;
else C[o].sum = 0;
C[o].rec = ds;
return ;
}
int mid = (l + r) >> 1;
if(ind <= mid) update(o << 1, l, mid, ind);
else update(o << 1 | 1, mid + 1, r, ind);
push_up(o);
}
seg query(int o, int l, int r, int ql, int qr) {
if(l > qr || r < ql) {
seg s;
for(int i = 0; i < 3; i++) s.l[i] = s.r[i] = s.sum = s.rec = 0;
return s;
}
if(l >= ql && r <= qr) return C[o];
int mid = (l + r) >> 1;
seg ans;
seg p1 = query(o << 1, l, mid, ql, qr);
seg p2 = query(o << 1 | 1, mid + 1, r, ql, qr);
ans.rec = (p1.rec + p2.rec) % 3;
ans.sum = p1.sum + p2.sum;
for(int i = 0; i < 3; i++) {
ans.l[i] = p1.l[i];
ans.r[i] = p2.r[i];
int ls = (i - p1.rec), rs = (i - p2.rec);
if(ls < 0) ls += 3; if(rs < 0) rs += 3;
ans.l[i] += p2.l[ls];
ans.r[i] += p1.r[rs];
}
for(int i = 0; i < 3; i++) ans.sum += p1.r[i] * p2.l[(3 - i) % 3];
return ans;
}
int main() {
while(scanf("%d %d", &n, &m) != EOF) {
for(int i = 1; i <= n; i++) scanf("%d", &bit[i]);
build(1, 1, n);
while(m--) {
scanf("%d", &op);
if(op == 1) {
scanf("%d", &x);
update(1, 1, n, x);
} else {
scanf("%d %d", &x, &y);
ll ans = query(1, 1, n, x, y).sum;
printf("%lld\n", ans);
}
}
}
return 0;
}
E - 跳格子
思路:
因为往回跳的时候只能跳到已经跳的格子上去,那么如果往回跳的时候结束点
sum
s
u
m
跳到了
y
y
相当于到了
y
y
这个位置需要跳回去的方案数,那么设:结束点在
x
x
这个格子时跳回之前方案数,设
ti
t
i
表示一次最多跳
n
n
步的要跳步长的方案数,那么
dp[x]=dp[x−1]⋅t1+dp[x−2]⋅t2+....+dp[x−m]⋅tm
d
p
[
x
]
=
d
p
[
x
−
1
]
⋅
t
1
+
d
p
[
x
−
2
]
⋅
t
2
+
.
.
.
.
+
d
p
[
x
−
m
]
⋅
t
m
,记忆化搜索即可,注意他是从
0
0
跳到然后跳回
0
0
<script type="math/tex" id="MathJax-Element-8386">0</script>。
#include<bits/stdc++.h>
typedef long long ll;
const ll mod = 233333333;
const int maxn = 4e5 + 10;
using namespace std;
ll dp[maxn], tot[20];
void init(int n) {
tot[0] = 1;
for(int i = 1; i < 20; i++) {
for(int j = 1; j <= n; j++) {
if(i - j < 0) continue;
tot[i] = (tot[i] + tot[i - j]) % mod;
}
}
}
ll dfs(int now, int n, int m) {
if(now < 1) return 0;
if(now == 1) return dp[now] = 1;
ll &res = dp[now];
if(res != -1) return res;
res = 0;
for(int i = 1; i <= m; i++) {
//if(i > n) break;
//res += dfs(now - i, n, m) * tot[i] % mod;
ll kk = dfs(now - i, n, m);
//printf("dp[%d] += %lld(dp[%d]) * %lld(tot[%d])\n", now, kk, now - i, tot[i], i);
res += kk * tot[i] % mod;
if(res >= mod) res -= mod;
}
return res;
}
int main() {
int T;
scanf("%d", &T);
while(T--) {
int s, n, m;
memset(dp, -1, sizeof dp);
scanf("%d %d %d", &s, &n, &m);
s++; memset(tot, 0, sizeof tot);
init(n);
//for(int i = 1; i < 10; i++) printf("tot[%d] = %lld\n", i, tot[i]);
ll ans = dfs(s, n, m);
//for(int i = 1; i <= s; i++) printf("dp[%d] = %lld\n", i, dp[i]);
printf("%lld\n", ans);
}
return 0;
}