D

题目

https://atcoder.jp/contests/arc144/tasks/arc144_d

有多少个映射f满足

定义域[0..2^n-1]

值域[0..k]

且值域内任意值x,y满足

f(x) + f(y) = f(x & y) + f(x | y)

范围

n 3e5

k 1e18

2s

1024mb

题解

我的思路

f(…0) + f(1) = f(…1) + f(0)

f(…0.) + f(10) = f(…1.) + f(0)

因此 自由元素 f(0) f(1) f(2) f(4) …

把 f(0..2^{n-1}-1)看作整体, 那么 f(2^{n-1}..2^{n}-1) 可以看作它的平移

显然有

dp[i][min][max] = sum{dp[i-1][min][min..max]} + sum{dp[i-1][min..max][max]} - dp[i-1][min][max]

然而这 $O(nk^2)$ 显然时间空间都接受不了

不知道怎么化简

题解

跟着上面思路 如果 f(0) = 0 , 那么 f(x) = 按二进制拆开x的 sum f(bit)

相当于 f(1) + f(2) + f(4) + f(8) … <= k 插板组合数

如果f(0) != 0, 令 f(0) = V

那么令 g(x) = f(x) - V

那么g(x) 也满足条件, -V <= g(x) <= K - V

0 <= sum g(任意2幂) + V <= k

sum 正g(2幂) + V <= k

sum 负g(2幂) + V >= 0


然后就是

枚举f(0) 的值V

枚举正数个数i, i个正的和 $\le k-V$, 因为小于等于 所以不妨把k-V-和+1 看作一个新的正数,相当于 i+1个正数 和 = k-V+1, 那就是k-V空隙插i个板子

枚举负数个数j,同理

$\sum_{V=0}^k \sum_{i=0}^n \sum_{j=0}^{n-i} \binom{n}{i} \binom{n-i}{j} \binom{k-V}{i} \binom{V}{j}$

$\sum_{i=0}^n \sum_{j=0}^{n-i} \binom{n}{i} \binom{n-i}{j} (\sum_{V=0}^k \binom{k-V}{i} \binom{V}{j})$

$\sum_{i=0}^n \sum_{j=0}^{n-i} \binom{n}{i} \binom{n-i}{j} (\sum_{V=j}^{k-i} \binom{k-V}{i} \binom{V}{j})$

右侧括号里,可以想成$k+1$个球, 选出$i+j+1$个球的组合方案数

我们去枚举被选的第$i+1$个球的位置$p$, $p \in [i+1,k+1-j]$

那么左侧有$p-1$个, 需要选出$i$个, 右侧有$k + 1 - p$个需要选出$j$个

令$V = k + 1 - p$即和现在表达式一致了

所以

$\sum_{i=0}^n \sum_{j=0}^{n-i} \binom{n}{i} \binom{n-i}{j} \binom{k+1}{i+j+1}$

$\sum_{i=0}^n \sum_{j=0}^{n-i} \binom{i+j}{i} \binom{n}{i+j} \binom{k+1}{i+j+1}$

$\sum_{i+j \le n} \binom{i+j}{i} \binom{n}{i+j} \binom{k+1}{i+j+1}$

二项式和

$\sum_{s = 0}^n 2^s \binom{n}{s} \binom{k+1}{s+1}$

就可以做了

代码

https://atcoder.jp/contests/arc144/submissions/33279666

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
#define MOD 998244353
#define rep(i,a,n) for (ll i=a;i<(ll)n;i++)
#define per(i,a,n) for (ll i=n;i-->(ll)a;)
#define pb push_back

ll read(){ll r;scanf("%lld",&r);return r;} // read

ll mul(ll a,ll b){
a %= MOD;
b %= MOD;
return a * b % MOD;
}

ll add(ll a,ll b){
a %= MOD;
b %= MOD;
return (a + b) % MOD;
}

ll normal(ll a){
return (a%MOD + MOD)%MOD;
}

ll mypow(ll v,ll pwr){
v%=MOD;
ll r = 1;
while(pwr){
if(pwr%2) (r*=v)%=MOD;
(v*=v)%=MOD;
pwr/=2;
}
return r;
}

ll fac[300010] = {1};

ll binom(ll n,ll m){
if(m > n) return 0;
return fac[n] * mypow(fac[m],MOD-2) % MOD * mypow(fac[n-m],MOD-2) % MOD;
}

int main(){
rep(i,1,300005) fac[i] = mul(fac[i-1],i);
int n = read();
ll k = read();
ll ans = 0;
ll binomk1s1 = 1; // k 很大
rep(s,0,n+1){
if(s > k) break;
binomk1s1 = mul(binomk1s1,mul(k+1-s,mypow(s+1,MOD-2)));
ans = add(ans,mul(mul(mypow(2,s),binom(n,s)),binomk1s1) );
}

printf("%lld\n", normal(ans));
return 0;
}

E

题目

N点,M边有向图

有向边 都是从小节点指向大节点的(无自环,无重边)

输入一个初始W[1..N],其中如果是Wi=-1,就可以取任何值,否则按给定的来

点i 权重 Wi

求最大从1到N的所有路径的权重和的gcd

如果gcd可能无限大则输出-1

不需要输出W的方案

范围

N 3e5

M 3e5

至少存在一条路径

初始Wi [1…10e12]

2s

1024MB

题解

我的思路

首先 如果存在一条完全给定的 1->N的权重和则有限大,否则可能无限大(A->B, B->C->D, B->D, 其中B任意,而后面指定两条路径不等, 那么gcd也是有限大)

对于有限大, 因为有向边全是从小指向大, 所以不成环只有拓扑关系

在没有具体值的情况, 并不能对求和的表达式计算gcd

所以考虑有没有可能反过来

反过来指定gcd, 1个问题是并没有二分性质, 每条边的可能性是考虑mod gcd,也是gcd个, 量级也不会太小

对于直接有指定W路径的相对好一些, 其中gcd一定是它的约数,这样情况会少一些,但是Ai和M都很大,即使给定的路径W和也可以达到3e17

题解

首先干掉 1不能到达, 和 不能到达N的点(无效的点), 防止无效的点影响找环的结果

如果k是答案,那么也就是所有路径 和 %k == 0

那么假设 到点u, (1 -> u)%k = p[u] 是唯一的

那么所有直接相邻的 vi->u, 有 p[vi] + w[u] = p[u] (mod k)

说明p[vi] 全部一样

这样, 就并查集了!

然后p[n] = 0, 所以可以加0号点 W[0] = 0, 路径0->1


有直接相邻u->v,且w[v]给定,

说明 两个并查集里的 的union[u] + 3 = union[v]


变成了 一些点, 和一些有权有向边

找所有环, 求gcd, 并不能找所有环,但是gcd性质上, 所有环gcd = 所有(树+回边) gcd

所以就可以搞了

环即可能由0产生, 也会有形如A->B->C->D, A->D, 这样产生


无环 就任意都可以, -1


然后有向图找环 我真不会写

学了一下apiad巨佬的代码, 发现是所有边建立正向和负向, 保证任何简单复杂路径 = 端点简单路径和即 全部像是无向图的有向图, 这样任何一个点可以作为根

代码

https://atcoder.jp/contests/arc144/submissions/33329393

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
#define rep(i,a,n) for (int i=a;i<(int)n;i++)
#define pb push_back

ll read(){ll r;scanf("%lld",&r);return r;} // read
ll gcd(ll a,ll b){return b == 0?a:gcd(b,a%b);}

const int N = 300000;

int n,m;

vector<int> u2v[N+10]; // 正向边
vector<int> v2u[N+10]; // 反向边

ll w[N+10];
int invalid[N+10]; // 1 无效, 0 有效, -1 未访问
int fa[N+10]; // 并查集

// 并查集
int getfa(int v){ return v == fa[v]?v:(fa[v] = getfa(fa[v]));}
void link(int u,int v){ fa[getfa(u)] = getfa(v);}
// 计算不可达
bool dfs1n(int u){
int &r = invalid[u];
if(r != -1) return r;
if(u == n) return r = 0;
r = 1;
for(auto v:u2v[u]) if(!dfs1n(v)) r = 0;
return r;
}

// 移除不合法
vector<int> rminvalid(const vector<int> &arr){
vector<int> ret = {};
for(auto u: arr) if(!invalid[u]) ret.pb(u);
return ret;
}

vector<pair<int,ll> > p2[N+10];
int vis[N+10];
ll dis[N+10]; // 和根的距离, root distance

void dfs(int idx,ll d,ll & ans) {
if(vis[idx]) {
// (环长 = 多树边 + 1回边), (重边), 多回边的环 gcd = 拆分的多个单回边环的gcd的gcd
ans = gcd(ans, abs(d - dis[idx]) );
return ;
}
vis[idx] = true;
dis[idx] = d;
for(auto [v,s]:p2[idx]) dfs(v, d + s, ans);
}

int main(){
n = read();
m = read();
iota(fa,fa+n+1,0); // fa[i] = i
fill(invalid+1,invalid+n+1,-1); // invalid[i] = -1
rep(i,1,m+1) { // u -> v
int u = read();
int v = read();
u2v[u].push_back(v);
v2u[v].push_back(u);
}
rep(i,1,n+1) w[i] = read();
// 筛无效点
dfs1n(1);
rep(u,1,n+1) if(!invalid[u]) u2v[u] = rminvalid(u2v[u]);
rep(v,1,n+1) if(!invalid[v]) v2u[v] = rminvalid(v2u[v]);
// n -> 1
u2v[n].pb(1);
v2u[1].pb(n);

// 找所有点的源点, 计算并查集
rep(v,1,n+1) if(!invalid[v]) rep(i,1,v2u[v].size()) link(v2u[v][i-1], v2u[v][i]);
// 根据给定w 建立新的图
rep(v,1,n+1) if(!invalid[v] && w[v] != -1){
int tov = getfa(v); // 并查集中的点
int fromv = getfa(v2u[v][0]); // assert(v2u[tov].size()); 因为都可达所以每个点一定有前置点
// 全部双向边 辅助在有向图中找所有环的gcd
p2[fromv].pb({tov, w[v]}); // 正向
p2[tov].pb({fromv, -w[v]}); // 负向
}
// 找环
ll ans = 0;
rep(i,1,n+1) if(!invalid[i] && !vis[i]) dfs(i,0,ans);
printf("%lld\n",ans == 0? -1: abs(ans));
return 0;
}


总结

D

这个对f(0) = 0特殊讨论 ,在 讨论f(0) 不为零的转化, 就是 一个特殊边界讨论 + 向特殊转移的问题

然后什么神奇的范德蒙恒等式(这里并不是, 但有一点思路相近的感觉

$\binom{n+m}{k} = \sum_{i=0}^k \binom{m}{i} \binom{n}{k-i}$

其实就是考虑$(1+x)^{m+n}$的$x^k$系数和 $(1+x)^m\cdot (1+x)^n$的$x^k$的系数

总之还有一些组合数的技巧,不是光有了初始表达式就可以的

E

讨论满足目标条件的 相邻转移

然后这个有向找所有环的gcd 也是学了一手, 改成正负双向

参考

官方题解

https://www.bilibili.com/video/BV1aB4y1a74j

Newton’s method on polynomials

希望找到$F(x)$,使得$A(F(x))=0$

  • 已知 $F_d(x) \equiv F(x) \mod (x^d)$
  • 已知 $[x^0] F(x)$, 即$F_1(x)\equiv [x^0]F(x) \pmod{x^1}$

$A(F(x)) = A(F(x)-F_d(x)+F_d(x))$

$= A(F_d(x)) + A’(F_d(x))(F(x)-F_d(x)) + O((F(x)-F_d(x))^2)$

相当于$F(x)$在点 $F_d(x)$展开,(总觉得有点小怪,这里相当于认为所有的都是 能表示成 幂级数(生成函数)的表达?)

那么两边同时 $\mod x^{2d}$

$A(F(x)) \equiv A(F_d(x)) + A’(F_d(x))(F(x)-F_d(x)) \pmod{x^{2d}}$

因此我们可以从 $F_d(x)$推出 $F_{2d}(x)$

$0\equiv A(F_{2d}(x)) \equiv A(F_d(x)) + A’(F_d(x))(F_{2d}(x)-F_d(x)) \pmod{x^{2d}}$

$F_{2d}(x)\equiv F_d(x) - \frac{A(F_d(x))}{A’(F_{d}(x))} \pmod{x^{2d}}$

用处

  • The Inverse of Formal Power Series
  • 计算 在 给定 $\mod x^t$ 下的 $\frac{1}{f(x)}$

那么 利用上面的

我们想求 $\frac{1}{f(x)}$

那么令 $F(x)=\frac{1}{f(x)}$

对应上面的就是 $A(F(x))=F(x)\cdot f(x)-1 = 0$


带入上面的结论

$F_{2d}(x)=F_d(x)-\frac{F_d(x)f(x)-1}{f(x)}$

???? 对求逆似乎没有帮助, 感觉题解这里似乎哪里不对?


问题在于 令$A$, 重新定义一下$A$

$A(F(x)) = \frac{1}{F(x)}-f(x)$

即 $\displaystyle F_{2d}(x) \equiv F_{d}(x) - \frac{\frac{1}{F_{d}(x)}-f(x)}{-\frac{1}{F_d(x)^2}} \pmod{x^{2d}}$

$F_{2d}(x)\equiv 2F_{d}(x) - F_d(x)^2f(x) \pmod{x^{2d}}$

相关

https://atcoder.jp/contests/abc260/editorial/4469

abc 260 ex

abc 345 ex

https://atcoder.jp/contests/abc259/tasks/

G - Grid Card Game

题目

HxW的数阵

选择任意的一些行和一些列, 让被选到的所有格子的和最大(同时被行和列选只统计一次)

且 限制条件,如果格子上的值为负那么 不能同时选它的行和列

范围

H,W [1,100]

Aij [-1e9,+1e9]

2s

1024mb

题解

我的思路

显然 纯非负的行和列一定被选,

所以可以预处理让剩下的所有行列至少包含一个负数

然后考虑说纯选行或选列

但是显然有反例

1
2
3
  1     -1    100
-1 1 -10000
100 -10000 1

这种情况 最优的是选1行和1列


然后另一个性质是, 显然 和为非正的行和列不会被选, 因为 如果即选了行又选了列,重叠部分非负 两个都选的和是 小于 两个分别的和的

然后没思路了

题解

首先我们令 结果 = 全部正值 都被选了, 考虑变化对结果的影响

  1. 如果一个正的没有被选, 那么它的行列没有直接被选, -Aij
  2. 如果一个负值被选了, 那么它的行和列有一个被选, Aij

如果 Aij < 0 被选了,

  • 如果是行被选, 那么 影响的是 加上 i行的所有负数

  • 如果是列被选, 那么 影响的是 加上 j列的所有负数


于是改变题目

初始分 = 正数和

那么如果 选了i行, 则 损失 i行的所有负值的和

那么如果 选了j列, 则 损失 j列的所有负值的和

对于正的单元没被选的 损失上面值的代价

对于负的单元, 不恩那个同时选行和列

答案 = 正数和 减去 下面网络流的最小割

点:

R[1..H]

C[1..W]

源S,汇T

边:

S->R[i], 容量 行i的所有负值和的绝对值

C[j]->T, 容量 行j的所有负值和的绝对值

R[i] -> C[j] 如果是 Aij > 0, 则 权重 Aij

C[j] -> R[i] 如果是 Aij < 0, 则 权重 无限大

这样考虑

对于 Aij > 0

S->R[i]->C[j]->T 在最小割中, 至少有一条边被割(说至少是因为, 可能 R和T一个集合,S和C一个集合)

对 Aij < 0

也就是最小割一定不会同时割S->R[i],和C[j]->T, 因为如果这样割了

意味着, S,C[j] 是一个集合,R[i],T是一个集合, 就有 C[j] -> R[i] 的无限大, 就不会是最小割了

对于Aij < 0 一定是 S->R[i]C[j]->T, 表示

也就是对于Aij < 0, 至多一个成为割边


然后最小割 = 最大流 Dinic, 或者直接调用官方的maxflow

代码

https://atcoder.jp/contests/abc259/submissions/33171094

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <bits/stdc++.h>
#include <atcoder/maxflow>
using namespace std;

typedef long long ll;
#define rep(i,a,n) for (ll i=a;i<(ll)n;i++)
#define per(i,a,n) for (ll i=n;i-->(ll)a;)

ll read(){ll r;scanf("%lld",&r);return r;} // read

int n;
int m;
const int N = 100;

const ll inf = 0x3f3f3f3f3f3f3f3f;
int h, w; // 输入
ll a[N+10][N+10]; // 输入
ll rsum[N+10], csum[N+10]; // 行列负数绝对值和

int main() {
h = read();
w = read();
rep(i,1,h+1){
rep(j,1,w+1) a[i][j] = read();
}
atcoder::mf_graph<ll> g(h+w+2); // 传入点数
int S = 0; // 源
int T = h+w+1; // 汇
ll ans = 0;
rep(i,1,h+1){
rep(j,1,w+1){
if(a[i][j] >= 0){
ans += a[i][j];
g.add_edge(i, h+j, a[i][j]); // R[i] -> C[j], a[i][j]
} else {
g.add_edge(h+j, i, inf); // C[j] -> R[i], inf
rsum[i] += -a[i][j];
csum[j] += -a[i][j];
}
}
}
rep(i,1,h+1) g.add_edge(S, i, rsum[i]); // S -> R[i], 行负数和的绝对值
rep(j,1,w+1) g.add_edge(h+j, T, csum[j]); // C[j] -> T, 列负数和的绝对值
printf("%lld\n", ans - g.flow(S, T));
return 0;
}

H/Ex - Yet Another Path Counting

NxN的矩阵

每次向右或向下走

问有多少种路径,头和尾所在位置的数字相同

范围

n 400

aij [1,N^2]

2s

1024 MB

题解

我的思路

最朴素就是 对不同值分开处理,直接变成 每个值=> 所有位置

然后 (i0,j0) => (i1,j1) 就是组合数

问题是, 如果一个一算,是(N^4)的样子会TLE

考虑是否有办法把结果变成统计合并

题解

分块

更朴素的纯色+dp是, O(n^2)的

所以对于每个颜色根据个数来做不同处理

如果当前颜色点个数 <= n

显然用我的思路里两两去算, 复杂度不超过O(个数^2),这一类的总复杂度小于O(sum{个数^2}) <= O(sum{n * 个数})<= O(n * sum{个数}) <= O(n^3)

如果当前颜色个数 > n , 那就直接dp,也不超过O(n^2), 而这种最多n种颜色最多O(n^3)

综上

都在n^3内

代码

https://atcoder.jp/contests/abc259/submissions/35946797

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <bits/stdc++.h>
#include <atcoder/modint>
using mint=atcoder::modint998244353;
using namespace std;
#define rep(i,a,n) for(int i=a;i<(int)n;i++)
int read(){int r;scanf("%d",&r);return r;}
int a[410][410];
vector<pair<int,int>>b[160010];//value->{i,j}
mint c[410][410];
int main() {
int n=read();
rep(i,0,n)rep(j,0,n)b[a[i][j]=read()].push_back({i,j});
rep(i,0,n)c[0][i]=c[i][0]=1;
rep(i,1,n)rep(j,1,n)c[i][j]=c[i-1][j]+c[i][j-1];
mint s=0;
rep(k,1,n*n+1){//kolour
if((int)size(b[k])<=n){//点少枚举点对
for(auto[x0,y0]:b[k])for(auto[x1,y1]:b[k])if(x0<=x1&&y0<=y1)s+=c[x1-x0][y1-y0];
} else{//点多二维dp
auto t=vector(n,vector<mint>(n,0));
rep(i,0,n)rep(j,0,n){
t[i][j]=(i?t[i-1][j]:0)+(j?t[i][j-1]:0)+(a[i][j]==k);
if(a[i][j]==k)s+=t[i][j];
}
}
}
printf("%d\n",s.val());
return 0;
}

总结

G

感觉 完全对网络流类型不熟,即使看了答案也仅是证明, 感觉没有系统的练习相关的建图, 还是不知道从何而起

这里相当于网络流求的是尽可能删除得小的, 利用了 最小割 = 最大流 , 这也是一个点,要求最小值的时候可以考虑让图能含义到最小割

然后就是atcoder内置的maxflow真香啊

Ex

有一说一感觉比G简单

这个分类的第一个复杂度上限推导还是很有意思,有点像之前树上左右部分平方的dp总复杂度是n3不是n4的感觉

参考

官方题解

https://www.bilibili.com/video/BV1KW4y1S7NA

这次Div2的D,E都不会了

D

https://codeforces.com/contest/1699/problem/D

长度n数组A

每次操作可以删除相邻且不同的两个值, 剩下的拼在一起, 多次操作

要让最终的数组值全部相同,求最大长度

范围

n 5000

2s

256MB

题解

我的思路

如果我们指定哪个值留下来

假设是v

那么 考虑两个v之间的其它值 v …. v

如果其中有值x出现次数超过一半, 那么剩下的必然是x - 非x

否则,如果是奇数个剩下任意一个, 偶数个则全部清除

最后可以得到一个 v 非v v 非v v …

的多段结果

然后我并没有什么办法 处理这个

如果有办法就是n^2 的总复杂度了

题解

首先如果一个数出现次数超过一半,那最终剩下的一定是它,所以这种情况不用猜留哪个

如果整个长度是偶, 且没有数出现次数超过一半,那么可以被完全删除

然后通过O(n^2) 计算所有区间 最多出现的数字,或者全部可消除

啊 我知道啊


dp[i] 表示a[0…i]删除以后 结果包含a[i] 的最大长度

初始化 如果[0..i-1] 能完全删除 dp[i] = 1, 否则 dp[i] = -INF

如果j < i, a[i] == a[j][j+1..i-1] 能完全删除

dp[i] = max(dp[j]+1)

所以最后就是求所有中的最大的且后缀可删除rm[j..end] == true, 相当于找结果的末尾位置

代码

https://codeforces.com/contest/1699/submission/162852461

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
#define rep(i,a,n) for (ll i=a;i<(ll)n;i++)
#define per(i,a,n) for (ll i=n;i-->(ll)a;)
#define pb push_back

ll read(){ll r;scanf("%lld",&r);return r;} // read

int n;

int a[5010]; // 5000 n^2
bool rm[5010][5010]; // rm[i][j] = [i..j] 完全删除
int dp[5010]; // 前[0..i] 删完剩下 a[i] 的个数

const int INF = 0x3f3f3f3f;

void w(){
n = read();
rep(i,0,n) a[i] = read();
rep(i,0,n){
fill(rm[i],rm[i]+n,false);
vector<int>cnt(n+1,0); // 次数统计
int maxc = -1; // 出现最多的
rep(j,i,n){
cnt[a[j]]++;
if(maxc == -1 || cnt[maxc] < cnt[a[j]]) maxc = a[j];
if((j-i)%2 == 0)continue;
if(cnt[maxc] <= (j-i+1)/2) rm[i][j] = true;
}
}
rep(i,0,n) dp[i] = (i == 0 || rm[0][i-1]) ? 1: -INF; // 初始化
int ans = 0;
rep(i,0,n){
rep(j,0,i){
if((i-j)%2==0)continue;
if(a[i] != a[j]) continue;
if(j == i-1 || rm[j+1][i-1]) dp[i] = max(dp[i], dp[j]+1);
}
if(i == n-1 || rm[i+1][n-1]) ans = max(ans,dp[i]); // 后续可删
}
printf("%d\n",ans);
}

int main(){
int t = read();
while(t--) w();
return 0;
}

E

给你长度n数组a

每次你可以把任意一个值v=a乘b,拆成a,b,

求 min(max(数组) - min(数组))

范围

n 1e6

ai [1..5e6]

4s

256MB

题解

我的思路

直接想 有点像是说, 能否把每个数拆分进[l..r] 之间

变个方向就是 给定l,求最小的r

那么考虑l的变化

因为任意两个ai,aj的拆分方案互不影响, 考虑单个 v 拆成最小>=l时,最大r的最小值的

显然l增大时,r 非严格单增, 且l <= min(ai)的

而问题是让区间尽量小

区间长度并没有单调性或凹凸性, 想法方向GG


第二个想法是

我直接拆每个数, 去计算每个数的map[间隔] = vector< pair<最小,最大> >

比如 4: [0] = { { 2 , 2 } , { 4 , 4 } }

但不知道怎么拆, dfs暴力?

以及拆分后怎么在不同ai之间组合

题解

和我第一个想法类似但是倒着for最小的因子

因为不同v的拆法互不影响,考虑一个具体的原数组中出现过的 v

若当前最小因子恰好为i, 那么

如果 v不是i的倍数, 则,之前v怎么拆分就怎么拆分

如果 v < i^2, 显然不能拆,如果拆了 另一个因子就会小于i

v >= i^2v = ik , 那么会拆成i 和 k, 而对于k可能也有拆的方案

我们直接记录dp[k] = 当前拆分方案中, 最大的因子

dp[ik] = min(old dp[ik], dp[k]), 其中k >= i

这里要注意的是当一个数v=ik是i的倍数,它按i拆开仅是可能让最大因子更小,而不是一定, 所以要和之前的dp[v] 比较


而最大值, 显然是非严格单调递减, 我们只需要 统计每个值拆分后的最大因子(也是非严格单调递减)出现次数, 就能知道最大值

代码

https://codeforces.com/contest/1699/submission/162860620

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
#define rep(i,a,n) for (ll i=a;i<(ll)n;i++)
#define per(i,a,n) for (ll i=n;i-->(ll)a;)
#define pb push_back

ll read(){ll r;scanf("%lld",&r);return r;} // read
const int N = 5000000;
bool appear[N+10]; // 在数列中出现过
int mxval[N+10]; // [v] = v当前被拆分出来的更大的因子 , 运算过程中每个值的变化是非严格单调递减
int cnt[N+10]; // 遍历过程中 每个ai拆分后对应最大因子 的 次数统计
int n;
int m;

void w(){
// clear
fill(appear,appear+m+1,false);
fill(cnt,cnt+m+1,0);
fill(mxval,mxval+m+1,0);

n = read();
m = read();

int mn = m; // 最小
int mx = 0; // 最大
rep(i,0,n){
int x = read();
appear[x] = true;
cnt[x] = 1;
mn = min(mn, x);
mx = max(mx, x);
}
iota(mxval,mxval+mx+1,0); // mxval[i] = i; 默认都未被拆分
int ptr = mx; // 最大值
ll ans = mx - mn;
per(i,1,mx+1){
for (ll j = i * i; j <= mx; j += i) { // j = i * (k>=i) , j 拆分成 i 和 k, k可能继续能拆
// 移除原有拆分方案
if (appear[j]) cnt[mxval[j]]--; // 从真的统计上讲 应该是 [i]--, [mxval[j]]--, 但i <= mxval[j] 所以 这里 中间一段不影响结果
// 计算新的最优方案
mxval[j] = min(mxval[j], mxval[j / i]); // i 不一定是最小的, 所以吆喝之前的比较
// 加入新的拆分方案
if (appear[j]) cnt[mxval[j]]++;
}
while (cnt[ptr] == 0) ptr--;
if (i <= mn) ans = min(ans, ptr - i); // 最小值为i, 最大值为ptr
}
printf("%lld\n",ans);
}

int main() {
int t = read();
while (t--) w();
}

总结

D

核心其实还是dpi可以和ai挂钩,因为其它什么区间可删除都想到了, 感觉应该还很常见的

E

倒着处理

只更新会影响的部分内容

因为遍历的i就是最小, 所以拆分统计, 不需要统计非最大因子以外的内容, 优化统计

参考

官方

F

题目

https://atcoder.jp/contests/abc258/tasks/abc258_f

网格,4临移动

如果 x=Bn的线上移动或者y=Bn的线上移动,(B的倍数), 单位距离代价1

其它情况单位距离代价k

求(sx,sy) -> (gx,gy) 的最小代价

范围

t 2e5 测试点

b,k [1,1e9]

sx,sy,gx,gy [0,1e9]

3s

1024mb

题解

我的思路

显然是个数学处理一下, 就做的题

两个点分开考虑

一个点计算它本身, 四个方向到x=bn or y=bn , 的点,再到四个角的点

点类型 (普通0,边点1,十字交点2)

(0-任何) 直接距离乘k

(边点 - 边/十字) = 在方向上同bn, 则x1, 否则直接距离乘k

(十字-十字) = 距离x1

写了二十多分钟

代码

https://atcoder.jp/contests/abc258/submissions/32973579

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
#define MOD 1000000007
#define rep(i,a,n) for (ll i=a;i<(ll)n;i++)
#define per(i,a,n) for (ll i=n;i-->(ll)a;)
#define pb push_back

ll read(){ll r;scanf("%lld",&r);return r;} // read

ll b;
ll k;

const int T_NORMAL = 0; // 普通
const int T_SIDE = 1; // 边
const int T_CROSS = 2; // 角

int calctype(ll i,ll j){
return (int)(i%b == 0) + (int)(j%b == 0);
}

vector<array<ll,3> > calc(ll i,ll j){ // i , j , dis
vector<ll> ai = {i};
vector<ll> aj = {j};
if(i%b) {
ai.pb((i/b)*b);
ai.pb((i/b+1)*b);
}
if(j%b) {
aj.pb((j/b)*b);
aj.pb((j/b+1)*b);
}
vector<array<ll,3> > res;
for(auto ni:ai){
for(auto nj:aj){
if(ni != i && nj != j){
res.pb({ni,nj, (abs(ni-i) + abs(nj-j) - max(abs(ni-i), abs(nj-j)))*k + max(abs(ni-i), abs(nj-j)) });
}else{
if(i % b != 0 && j%b != 0){
res.pb({ni,nj, (abs(ni-i) + abs(nj-j))*k});
}else{
res.pb({ni,nj, (abs(ni-i) + abs(nj-j))});
}
}
}
}
return res;
}

void w(){
b = read();
k = read();
ll si = read();
ll sj = read();
ll gi = read();
ll gj = read();
auto ijds = calc(si,sj);
auto ijdg = calc(gi,gj);
ll ans = 0x3f3f3f3f3f3f3f3f;

for(auto [i0,j0,d0]:ijds){
int t0 = calctype(i0,j0);
for(auto [i1,j1,d1]:ijdg){
int t1 = calctype(i1,j1);
if(t0 == T_NORMAL || t1 == T_NORMAL){
ans = min(ans,d0+d1+ (abs(i1-i0) + abs(j1-j0))*k);
}else if(t0 == T_SIDE || t1 == T_SIDE){
if(i0 == i1 && i0 % b == 0){
ans = min(ans,d0+d1+abs(j1-j0));
}else if(j0 == j1 && j0 % b == 0){
ans = min(ans,d0+d1+abs(i1-i0));
}else{
ans = min(ans,d0+d1+(abs(i1-i0)+abs(j1-j0))*k);
}
}else{ // == CROSS
ans = min(ans,d0+d1+abs(i1-i0)+abs(j1-j0));
}
}
}
printf("%lld\n",ans);
}

int main(){
int t = read();
while(t--) w();
return 0;
}

H/Ex

https://atcoder.jp/contests/abc258/tasks/abc258_h

序列X满足

  1. 所有元素正奇数
  2. 和为s
  3. X前缀和的值不在集合A中, 集合A大小为N

求满足的要求的序列X的个数

范围

n 1e5

ai [1,1e18]

s [1,1e18]

题解

我的思路

果然读错题, 读成了需要序列X长度也是N

实际上对序列长度无要求


不考虑X而是直接考虑X的前缀和

dp[v] = 构成v的方案数

dp[Aj] = 0

dp[0] = 1

递推关系

dp[v] = sum{dp[v-1]+dp[v-3]+ dp[v-5]+...}

f[i] = dp[i] + dp[i-2] + dp[i-4]

dp[v] = f[v-1] f[v] = dp[v] + f[v-2] = (v in A ? 0 :f[v-1]) + f[v-2]

所以可以直接算f 矩阵快速幂

然后问题是要处理掉v 在 A中的情况, 并且注意到v在A中是dp[v] == 0 并不意味f[v-1] == 0

1
2
3
(f[v-1] f[v-2]) (f[v] f[v-1])
(1/0 1 )
( 1 0 )

代码

https://atcoder.jp/contests/abc258/submissions/32981204

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
#define MOD 998244353
#define rep(i,a,n) for (ll i=a;i<(ll)n;i++)
#define per(i,a,n) for (ll i=n;i-->(ll)a;)
#define pb push_back

ll read(){ll r;scanf("%lld",&r);return r;} // read

int n;
ll s ;

ll a[100010];

// Wi = (f[i] f[i-1])

// (f[v] f[v-1]) = (f[v-1] f[v-2]) *
// (1/0 1 )
// ( 1 0 )

vector<vector<ll> > mul(vector<vector<ll> >m0,vector<vector<ll> >m1){
vector<vector<ll> > r = vector<vector<ll> >(m0.size(), vector<ll>(m1[0].size(),0));
assert(m0[0].size() == m1.size());
rep(i,0,m0.size()){
rep(j,0,m1[0].size()){
rep(k,0,m0[0].size()){
(r[i][j] += m0[i][k] * m1[k][j] % MOD) %= MOD;
}
}
}
return r;
}

vector<vector<ll> > mypow(vector<vector<ll> >m0,ll pwr){
vector<vector<ll> > r = {
{1,0},
{0,1}
};
while(pwr){
if(pwr%2) r = mul(r,m0);
m0 = mul(m0,m0);
pwr/=2;
}
return r;
}


int main(){
n = read();
s = read();
rep(i,0,n) a[i] = read();
a[n] = s; // dp[s] = f[s-1] => w[s][1]
n++;
vector<vector<ll> > w; // w[iw] = {f[iw], f[iw-1]}
ll iw = 1;
if(a[0] == 1) w = { {0,1} };
else w = { {1,1} };

rep(i,0,n){
ll ai = a[i];
if(iw == ai)continue;
if(iw == ai-1){
w = mul(w,{
{0,1},
{1,0}
});
iw = ai;
}else{
w = mul(
mul(w,mypow({
{1,1},
{1,0}
}, ai-iw-1)),{
{0,1},
{1,0}
});
iw = ai;
}
// printf("w[%lld] = {%lld %lld}\n",iw, w[0][0],w[0][1]);
}
printf("%lld\n",w[0][1]);
return 0;
}

总结

F

没啥难的

G

内置 bitset 优化一下效率就行了

Ex

也没啥难的

参考

官方题解

0%