根号分治
[ABC335F] Hop Sugoroku
题面翻译
有一排有 \(N\) 个方格,还有一个长度为 \(N\) 的序列 \(A\)。开始时,第一个方格被染成黑色,其他的方格为白色,并在第一个方格放上一个物品。
你可以进行若干次操作,操作如下:
- 当物品在方格 \(i\) 的时候,可以任意选择一个正整数 \(x\),并将物品移动到方格 \(i+A_i \times x\) 上,方格 \(i+A_i \times x\) 染成黑色。
求操作结束时,可以涂成黑色的可能方格集的数量,答案对 \(998244353\) 取模。
- \(f[i]\)表示以i结尾的所有可能的方案数
- 当\(a[i]<\sqrt{N}\)时,利用cnt数组直接记录,所有满足j=i+a[i]*x的j,都可以被转移,也就是j%a[i]=i%a[i],直接在cnt[a[i]][i%a[i]]存下当前的f
- 否则最多只会暴力转移\(\sqrt{N}\)个
#include<bits/stdc++.h>
#define fo(i,a,b) for(ll (i)=(a);(i)<=(b);(i)++)
#define fd(i,a,b) for(ll (i)=(a);(i)>=(b);(i)--)
#define mk(x,y) make_pair((x),(y))
#define pi pair<ll,ll>
#define eb emplace_back
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef __int128 i128;
typedef unsigned int uint;
const ll mo = 998244353;
const ll inf = 1ll << 60;
const int N = 2e5 + 5;
const int S = 450;
ll cnt[S + 1][S + 1], f[N], n, a[N];
void add(ll& x, ll y) {
x = (x + y) % mo;
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
fo(i, 1, n) cin >> a[i];
f[1] = 1;
fo(i, 1, n) {
fo(j, 1, S) add(f[i], cnt[j][i % j]);
if (a[i] > S) {
fo(j, 1, n) {
if (i + a[i] * j > n) break;
add(f[i + a[i] * j], f[i]);
}
}
else {
add(cnt[a[i]][i % a[i]], f[i]);
}
}
ll ans = 0;
fo(i, 1, n) add(ans, f[i]);
cout << ans;
return 0;
}
[ABC259Ex] Yet Another Path Counting
题面翻译
有 \(N\) 行 \(N\) 列的网格图,只能向下或向右走,合法路径的开端和结尾的格子上数字一样
找到合法路径条数,对 \(998244353\) 取模
- 首先显然不同颜色之间互不影响
- 设置一个阈值b,当这个颜色出现的次数小于b时,我们采用直接两两暴力的方法,时间复杂度为\(O(B^2)\),当次数大于b时,我们直接采用dp的方法,时间复杂度为\(O(n)\),\(nB^2=\frac{N^2}{B}N^2\),B=N时取等。
#include<bits/stdc++.h>
#define fo(i,a,b) for(ll (i)=(a);(i)<=(b);(i)++)
#define fd(i,a,b) for(ll (i)=(a);(i)>=(b);(i)--)
#define mk(x,y) make_pair((x),(y))
#define pi pair<ll,ll>
#define eb emplace_back
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef __int128 i128;
const ll mo = 998244353;
const ll inf = 1ll << 60;
const int N = 405;
vector<pi> e[N * N];
int n, a[N][N];
ll f[N][N], c[N * 2][N * 2];
void add(ll & x, ll y) {
x = (x + y) % mo;
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
fo(i, 1, n) {
fo(j, 1, n) {
cin >> a[i][j];
e[a[i][j]].eb(mk(i, j));
}
}
fo(i, 0, 2 * n) c[i][0] = 1;
fo(i, 1, 2 * n) {
fo(j, 1, i) {
c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mo;
}
}
ll ans = 0;
fo(i, 1, n * n) {
if (!e[i].size()) continue;
sort(e[i].begin(), e[i].end());
if (e[i].size() > n) {
fd(x, n, 1) {
fd(y, n, 1) {
f[x][y] = (f[x + 1][y] + f[x][y + 1] + (a[x][y] == i)) % mo;
if (a[x][y] == i) add(ans, f[x][y]);
}
}
}
else {
for (int x = 0;x < e[i].size();x++) {
for (int y = x;y < e[i].size();y++) {
if (e[i][x].fi <= e[i][y].fi && e[i][x].se <= e[i][y].se) {
add(ans, c[e[i][y].fi - e[i][x].fi + e[i][y].se - e[i][x].se][e[i][y].se - e[i][x].se]);
}
}
}
}
}
cout << ans;
return 0;
}
[CF1207F]emainder Problem
题面翻译
题目描述
给你一个长度为 \(500000\) 的序列,初值为 \(0\) ,你要完成 \(q\) 次操作,操作有如下两种:
1 x y
: 将下标为 \(x\) 的位置的值加上 \(y\)2 x y
: 询问所有下标模 \(x\) 的结果为 \(y\) 的位置的值之和
- 对于2操作,假如\(x \le \sqrt{n}\),我们直接用一个桶维护这些x的答案,反之直接暴力即可。
#include<bits/stdc++.h>
#define fo(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define fd(i,b,a) for (ll (i)=(b);(i)>=(a);(i)--)
#define eb emplace_back
#define pi pair<ll,ll>
#define mk(x,y) make_pair((x),(y))
#define lc (o<<1)
#define rc ((o<<1)|1)
#define A puts("Yes")
#define B puts("No")
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef double db;
const ll mo1=1e9+7;
const ll mo2=1e9+9;
const ll P=131;
const ll Q=13331;
const ll mo=998244353;
const ll inf=1ll<<60;
const int N=5e5+5;
const int M=710;
ll a[N],c[M][M];
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
int q;
int op,x,y;
scanf("%d",&q);
while (q--) {
scanf("%d %d %d",&op,&x,&y);
if (op==1) {
a[x]+=y;
fo(i,1,M-1) c[i][x%i]+=y;
}
else {
if (x<M) printf("%lld\n",c[x][y]);
else {
ll ans=0;
for (int i=y;i<N;i+=x) ans+=a[i];
printf("%lld\n",ans);
}
}
}
return 0;
}