2024ICPC沈阳VP记录
纯签到题,比较前四个队伍的最强队与后四个队伍的最强队,输出总决赛结果。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
string s[100];
int score[100];
void solve()
{
for(int i = 1; i <= 8; ++i)
{
cin >> s[i];
score[i] = read();
}
int id = 0;
for(int i = 1; i <= 4; ++i)
{
if(score[i] > score[id]) id = i;
}
int id2 = 0;
for(int i = 5; i <= 8; ++i)
{
if(score[i] > score[id2]) id2 = i;
}
if(score[id] > score[id2])
{
cout << s[id] << " beats " << s[id2];
}else
{
cout << s[id2] << " beats " << s[id];
}
}
int main()
{
int T = 1;
while(T--) solve();
return 0;
}
交换一个序列的任意两个元素会改变序列逆序对数的奇偶性,对A交换或者对B交换都可以看作,将B映射成严格递增的排列后,对A中的两个元素交换。
因此操作次数的奇偶性就是逆序对数的奇偶性。
而将区间 左移 位可以看作 次交换,只需要关注这个数的奇偶性。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 5e5 + 5;
int n, a[N], b[N], p[N];
struct BIT
{
int c[N];
BIT(){ memset(c, 0, sizeof(c)); }
void add(int pos, int val)
{
while(pos <= n) c[pos] += val, pos += (pos & -pos);
}
int query(int k)
{
int ans = 0;
while(k) ans += c[k], k -= (k & -k);
return ans;
}
}T;
void solve()
{
n = read();
for(int i = 1; i <= n; ++i) a[i] = read();
for(int i = 1; i <= n; ++i) b[i] = read(), p[b[i]] = i;
for(int i = 1; i <= n; ++i) a[i] = p[a[i]];
ll ans = 0;
for(int i = n; i >= 1; --i)
{
ans += T.query(a[i]);
T.add(a[i], 1);
}
ans %= 2;
for(int i = n; i >= 1; --i) T.add(a[i], -1);
printf((ans & 1) ? "A" : "B");
for(int i = 1; i < n; ++i)
{
char c;
scanf(" %c", &c);
int l = read(), r = read(), d = read();
if(((r - l) & 1) && (d & 1)) ans ^= 1;
printf((ans & 1) ? "A" : "B");
}
printf("\n");
}
int main()
{
int T = read();
while(T--) solve();
return 0;
}
逆向思考,问题转化为从 状态出发,经过所需状态的最小代价。
原问题给的操作数可以看作9种位运算,且必有一种可以被其他操作代替,因此从 到任意状态至多 次操作,暴搜出所有可能的操作序列,求出从 到任意状态的最小代价,记为 。
状态数只有 种,转移只需要考虑相邻状态,设 表示已经经过集合 中的状态,当前处于状态 所需的最小代价。
设 为经过 集合的状态的最小代价。
枚举子集即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = (1 << 16);
int a0, a1, a2, a3;
ll dp[N][16], mn[N], sum[16];
void dfs(int dep, int now, ll val)
{
if(now || (!now && dep > 1)) sum[now] = min(sum[now], val);
if(dep == 9) return ;
dfs(dep + 1, now ^ 2, val + a0);
dfs(dep + 1, now ^ 1, val + a0);
dfs(dep + 1, now ^ 4, val + a0);
dfs(dep + 1, now ^ 8, val + a0);
dfs(dep + 1, now ^ 3, val + a1);
dfs(dep + 1, now ^ 12, val + a1);
dfs(dep + 1, now ^ 5, val + a2);
dfs(dep + 1, now ^ 10, val + a2);
dfs(dep + 1, now ^ 15, val + a3);
}
void init()
{
for(int i = 0; i < (1 << 16); ++i) mn[i] = 0x7fffffffffffffff;
for(int i = 0; i < 16; ++i) sum[i] = 0x7fffffffffffffff;
for(int i = 0; i < (1 << 16); ++i)
for(int j = 0; j < 16; ++j)
dp[i][j] = 0x7fffffffffffffff;
dfs(1, 0, 0);
for(int i = 0; i < 16; ++i) dp[(1 << i)][i] = sum[i];
for(int i = 0; i < (1 << 16); ++i)
for(int j = 0; j < 16; ++j)
if((1 << j) & i)
for(int k = 0; k < 16; ++k)
if(!((1 << k) & i))
{
dp[i | (1 << k)][k] = min(dp[i | (1 << k)][k], dp[i][j] + sum[j ^ k]);
}
for(int i = 0; i < (1 << 16); ++i)
for(int j = 0; j < 16; ++j)
mn[i] = min(mn[i], dp[i][j]);
for(int i = 1; i < (1 << 16); ++i)
for(int j = i & (i - 1); j != i; j = i & (j - 1))
mn[j] = min(mn[j], mn[i]);
}
string s1, s2;
void solve()
{
int n = read();
int temp = 0;
for(int i = 1; i <= n; ++i)
{
cin >> s1, cin >> s2;
int temp2 = 0;
if(s1[0] == '1') temp2 ^= 1;
if(s1[1] == '1') temp2 ^= 2;
if(s2[0] == '1') temp2 ^= 4;
if(s2[1] == '1') temp2 ^= 8;
temp2 ^= 15;
temp ^= (1 << temp2);
}
printf("%lld\n", mn[temp]);
}
int main()
{
int T = read();
a0 = read(), a1 = read(), a2 = read(), a3 = read();
init();
while(T--) solve();
return 0;
}
将搬房间转化成两个楼层中的有向边,问题转化为在一个有向图上,环的边权和不为 ,找出所有位于或者能够到达这样的环的点。
大于一个点的强联通分量一定是环,对于每个强联通分量,将边权取正取反两次判负环,但是SPFA被卡掉了。
特殊点在于只需要判断环的边权和是否为 ,意味着从 到 的任意路径长度都与 到 的任意路径长度加和为 ,这符合势能的定义。
对于每个强联通分量任选起点BFS求每个点的势能,最后判断势能是否满足所有的边权。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const ll inf = 0x7fffffffffffffff;
const int N = 5e5 + 5;
int n, m, q;
int dp[N], f[N], du[N];
vector< pair<int, int> > to[N];
int sta[N], top, dfn[N], low[N], tot, belong[N], num;
vector<int> node[N];
void tarjan(int k)
{
dfn[k] = low[k] = ++tot; sta[++top] = k;
for(auto [v, w] : to[k])
{
if(!dfn[v]) tarjan(v), low[k] = min(low[k], low[v]);
else if(!belong[v]) low[k] = min(low[k], dfn[v]);
}
if(low[k] == dfn[k])
{
++num;
while(sta[top] != k)
{
belong[sta[top]] = num;
node[num].emplace_back(sta[top]);
--top;
}
belong[sta[top]] = num;
node[num].emplace_back(sta[top]);
--top;
}
}
ll dis[N];
int vis[N];
int BFS(int S)
{
if(node[belong[S]].size() == 1) return 0;
queue<int> q;
for(auto v : node[belong[S]]) dis[v] = inf, vis[v] = 0;
vis[S] = 1, dis[S] = 0;
q.push(S);
while(!q.empty())
{
int now = q.front();
q.pop();
for(auto [v, w] : to[now])
{
if(belong[v] != belong[now]) continue;
if(vis[v]) continue;
dis[v] = dis[now] + w, vis[v] = 1;
q.push(v);
}
}
for(auto u : node[belong[S]])
for(auto [v, w] : to[u])
if(belong[v] == belong[u])
if(dis[v] != dis[u] + w) return 1;
return 0;
}
vector<int> TO[N];
void solve()
{
n = read(), m = read(), q = read();
for(int i = 1; i <= m; ++i)
{
int a = read(), b = read();
a = (a % n + n) % n;
int B = ((a + b) % n + n) % n;
if(a == B && b != 0){ f[a] = 1; continue; }
to[a].emplace_back(pair<int, int>(B, b));
}
for(int i = 0; i < n; ++i) to[n].emplace_back(pair<int, int>(i, 0));
tarjan(n);
for(int i = 1; i <= num; ++i) dp[i] = BFS(node[i][0]);
for(int i = 0; i < n; ++i) dp[belong[i]] = max(dp[belong[i]], f[i]);
for(int i = 0; i < n; ++i)
for(auto [v, w] : to[i])
if(belong[v] != belong[i])
{
TO[belong[v]].emplace_back(belong[i]);
++du[belong[i]];
}
queue<int> Q;
for(int i = 1; i <= num; ++i)
if(du[i] == 0) Q.push(i);
while(!Q.empty())
{
int now = Q.front();
Q.pop();
for(auto v : TO[now])
{
dp[v] = max(dp[v], dp[now]);
--du[v];
if(du[v] == 0) Q.push(v);
}
}
while(q--)
{
int x = read();
x = (x % n + n) % n;
if(dp[belong[x]]) printf("Yes\n");
else printf("No\n");
}
}
signed main()
{
int T = 1;
while(T--) solve();
return 0;
}
结论:若 ,则无解,否则构造 ,。
证明1:
不妨设 ,则必有 ,使得 ,则第一行至多有 个不同的数数,分别为 ,但是不能少于 个,因此有 ,同理有 ,因此有 。
此时第一行为 ,第一列为 。
要求其中只有 是相同的,需要满足 ,因为此时对于 的最小正整数解为 。
证明2:
设 ,整理为 。
由于 ,所以只有 ,因此得证按照此构造,网格中没有两个相同的数。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
int n, m;
int gcd(int x, int y)
{
if(!y) return x;
return gcd(y, x % y);
}
void solve()
{
n = read(), m = read();
int d = gcd(n, m);
if(d != 1)
{
printf("No\n");
}else
{
printf("Yes\n");
for(int i = 1; i <= n; ++i) printf("%d ", ((i - 1) * m + 1) % (n * m));
printf("\n");
for(int j = 1; j <= m; ++j) printf("%d ", ((j - 1) * n + 1) % (n * m));
printf("\n");
}
}
signed main()
{
int T = read();
while(T--) solve();
return 0;
}
题解移步至 2024ICPC沈阳VP - lyrrr
首先求出每个点到根节点的距离 。
题目中可以将树枝修改成任意长度,所以可以取一些很大的数,使得整个子树内的 都不会和其他点再相同,这种操作类似于切掉子树。
考虑 以及他们的最近公共祖先 ,其中 ,此时必须修改 到 的路径上任意一个树枝,对应着切掉一个子树,显然被切掉的子树越大越好,此时只有两种选择:切掉 的左子树或者右子树。
因此从下往上考虑每一层的某个点是否需要切掉某个子树,由于至多切 次,所以总的情况数为 ,用 或者 暴力维护某个点子树内的所有 值,不断向上合并。
复杂度大概是
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int inf = 0x7fffffff;
const int N = 3005;
int n;
int son[N][2];
ll dis[N];
unordered_map<int, int> mp[N];
int ans;
void dfs(int dep, int now, int num)
{
if(now == 1){ ans = min(ans, num); return ; }
int flag = 0;
for(auto [x, y] : mp[now + 1])
{
if(mp[now].find(x) != mp[now].end())
{
flag = 1;
break;
}
}
// printf("dep = %d, now = %d, num = %d\n", dep, now, num);
// for(auto [x, y] : mp[now]) printf("%d ", x);
// printf("\n");
// for(auto [x, y] : mp[now + 1]) printf("%d ", x);
// printf("\n");
// printf("flag = %d\n", flag);
if(!flag)
{
for(auto [x, y] : mp[now]) mp[now >> 1][x] = y;
for(auto [x, y] : mp[now + 1]) mp[now >> 1][x] = y;
if(now == (1 << (dep + 1)) - 2) dfs(dep - 1, (1 << (dep - 1)), num);
else dfs(dep, now + 2, num);
mp[now >> 1].clear();
}else
{
if(n - dep + 1 < num + 1) return ;
for(auto [x, y] : mp[now]) mp[now >> 1][x] = y;
if(now == (1 << (dep + 1)) - 2) dfs(dep - 1, (1 << (dep - 1)), num + 1);
else dfs(dep, now + 2, num + 1);
mp[now >> 1].clear();
for(auto [x, y] : mp[now + 1]) mp[now >> 1][x] = y;
if(now == (1 << (dep + 1)) - 2) dfs(dep - 1, (1 << (dep - 1)), num + 1);
else dfs(dep, now + 2, num + 1);
mp[now >> 1].clear();
}
}
void solve()
{
ans = inf, n = read();
for(int i = 1; i < (1 << n); ++i) son[i][0] = (i << 1), son[i][1] = (i << 1) + 1;
for(int i = 2; i < (1 << (n + 1)); ++i) dis[i] = dis[i >> 1] + read();
for(int i = 1; i < (1 << (n + 1)); ++i) mp[i].clear();
for(int i = (1 << n); i < (1 << (n + 1)); ++i) mp[i][dis[i]] = 1;
dfs(n, (1 << n), 0);
if(ans == inf) printf("-1\n");
else printf("%d\n", ans);
}
signed main()
{
int T = read();
while(T--) solve();
return 0;
}
图中有两棵树,不妨设节点1所在树为A树,另一棵树为B树。
对树DFS遍历,恰好经过一条从A树走向B树的边,令这条边为 。
还可以向图中加入其他边,保证不改变DFS遍历顺序。
DFS遍历导致只能加返租边,对于一条边 , 可以向 子树中所有大于 的点连边。
将可以加入的边分为4类:
- 树内部的边,可以用线段树合并预处理。(与 的选择无关)
- 树内部的边,由于根变化,需要换根DP+DFS序上主席树预处理。(与 的选择无关)
- 到 的路径上的点(不包括 )与 树的点的连边,沿用2的主席树预处理。(与 的选择无关)
- 到 树的点的连边,与 的选择有关,可以预处理 表示 树中大于等于 的点的个数。
对于这些点,可选可不选,方案数为 。
特别注意的是,当 树的大小为 时,可以没有 ,也就是只需要 树内部连边。
更多细节请看代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define int ll
#define ull unsigned long long
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const int N = 2e5 + 5;
const ll mod = 998244353;
int n, belong[N], flag = 1;
vector<int> to[N];
vector<int> A, B;
int sum[N];
ll qpow(ll a, ll b, ll mod)
{
ll ans = 1;
while(b)
{
if(b & 1) ans = ans * a % mod;
b >>= 1;
a = a * a % mod;
}
return ans;
}
void dfs(int k, int fa, vector<int> &S)
{
S.emplace_back(k);
belong[k] = flag;
for(auto v : to[k])
{
if(v == fa) continue;
dfs(v, k, S);
}
}
struct Segment_Tree
{
#define ls(x) son[x][0]
#define rs(x) son[x][1]
int son[N * 60][2], sum[N * 60], root[N], tot;
Segment_Tree()
{
memset(son, 0, sizeof(son));
memset(sum, 0, sizeof(sum));
memset(root, 0, sizeof(root));
tot = 0;
}
void pushup(int k){ sum[k] = sum[ls(k)] + sum[rs(k)]; }
void merge(int &k, int p, int l, int r)
{
if(!k || !p){ k += p; return ; }
if(l == r){ sum[k] += sum[p]; return ; }
int mid = (l + r) >> 1;
merge(ls(k), ls(p), l, mid), merge(rs(k), rs(p), mid + 1, r);
pushup(k);
}
void update(int &k, int l, int r, int pos, int val)
{
if(!k) k = ++tot, ls(k) = rs(k) = sum[k] = 0;
sum[k] += val;
if(l == r) return ;
int mid = (l + r) >> 1;
if(pos <= mid) update(ls(k), l, mid, pos, val);
else update(rs(k), mid + 1, r, pos, val);
}
int query(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R) return sum[k];
int mid = (l + r) >> 1;
if(R <= mid) return query(ls(k), l, mid, L, R);
if(L > mid) return query(rs(k), mid + 1, r, L, R);
return query(ls(k), l, mid, L, R) + query(rs(k), mid + 1, r, L, R);
}
}T1;
struct Segment_tree
{
#define ls(x) son[x][0]
#define rs(x) son[x][1]
int son[N * 60][2], sum[N * 60], root[N], tot;
Segment_tree()
{
memset(son, 0, sizeof(son));
memset(sum, 0, sizeof(sum));
memset(root, 0, sizeof(root));
tot = 0;
}
void update(int &k, int p, int l, int r, int pos, int val)
{
k = ++tot, ls(k) = ls(p), rs(k) = rs(p), sum[k] = sum[p] + val;
if(l == r) return ;
int mid = (l + r) >> 1;
if(pos <= mid) update(ls(k), ls(p), l, mid, pos, val);
else update(rs(k), rs(p), mid + 1, r, pos, val);
}
int query(int k, int p, int l, int r, int L, int R)
{
if(L <= l && r <= R) return sum[k] - sum[p];
int mid = (l + r) >> 1;
if(R <= mid) return query(ls(k), ls(p), l, mid, L, R);
if(L > mid) return query(rs(k), rs(p), mid + 1, r, L, R);
return query(ls(k), ls(p), l, mid, L, R) + query(rs(k), rs(p), mid + 1, r, L, R);
}
}T2;
ll sum1, sum2[N], sum3[N], sum4[N], sum5;
// sum1 是A树中可以任意连的边,sum3是B树中,以i为根时可以任意连的边
// sum是B树中大于等于i的点的个数
// sum4是A树中,p连向B树时,1到fa_p能向B树连的边的数量
void dfs1(int k, int fa)
{
for(auto v : to[k])
{
if(v == fa) continue;
dfs1(v, k);
if(v != n) sum1 += T1.query(T1.root[v], 1, n, v + 1, n);
T1.merge(T1.root[k], T1.root[v], 1, n);
}
T1.update(T1.root[k], 1, n, k, 1);
}
int dfn[N], low[N], rk[N], tot;
void dfs2(int k, int fa)
{
dfn[k] = low[k] = ++tot, rk[tot] = k;
for(auto v : to[k])
{
if(v == fa) continue;
dfs2(v, k);
if(v != n) sum2[k] += T1.query(T1.root[v], 1, n, v + 1, n);
T1.merge(T1.root[k], T1.root[v], 1, n);
}
T1.update(T1.root[k], 1, n, k, 1);
}
void dfs3(int k, int fa)
{
for(auto v : to[k])
{
if(v == fa) continue;
sum3[v] = sum3[k];
if(v != n) sum3[v] -= T2.query(T2.root[low[v]], T2.root[dfn[v] - 1], 1, n, v + 1, n);
if(k != n) sum3[v] += T2.query(T2.root[dfn[v] - 1], T2.root[0], 1, n, k + 1, n);
if(k != n) sum3[v] += T2.query(T2.root[tot], T2.root[low[v]], 1, n, k + 1, n);
dfs3(v, k);
}
}
void dfs4(int k, int fa)
{
for(auto v : to[k])
{
if(v == fa) continue;
sum4[v] += sum4[k];
if(v != n) sum4[v] += T2.query(T2.root[tot], T2.root[0], 1, n, v + 1, n);
dfs4(v, k);
}
}
void solve()
{
n = read();
for(int i = 1; i < n - 1; ++i)
{
int u = read(), v = read();
to[u].emplace_back(v);
to[v].emplace_back(u);
}
dfs(1, 0, A); ++flag;
for(int i = 1; i <= n; ++i) if(!belong[i]) dfs(i, 0, B);
for(int i = 1; i <= n; ++i) if(belong[i] == 2) sum[i]++;
for(int i = n - 1; i >= 1; --i) sum[i] += sum[i + 1];
dfs1(1, 0);
dfs2(B.front(), 0);
for(int i = 1; i <= tot; ++i) T2.update(T2.root[i], T2.root[i - 1], 1, n, rk[i], 1);
for(auto now : B) sum3[B.front()] += sum2[now];
dfs3(B.front(), 0);
for(auto now : B) sum5 = (sum5 + qpow(2, sum3[now] + sum[now + 1], mod)) % mod;
dfs4(1, 0);
ll ans = 0;
for(auto now : A) ans = (ans + qpow(2, sum4[now] + sum1, mod) * sum5 % mod) % mod;
if((int)A.size() == n - 1) ans = (ans + qpow(2, sum1, mod)) % mod;
printf("%lld\n", ans);
}
signed main()
{
int T = 1;
while(T--) solve();
return 0;
}
经典做法之看到 考虑根号分治。
设 ,考虑对于 和 分别 。
设 表示前 个数中,最小的数大于等于 ,总高度为 的方案数。其中
转移时先做后缀和:
转移:
观察这个转移可以发现这个 的定义可以修改成,后一个数填 的方案数。
设 表示前 个数中,有 个贡献了高度,总高度为 的方案数。
转移时考虑费用提前计算,能够做贡献的数最小为 。
转移时先做如下处理:
已有的段整体+1。
转移:
以下是一些细节处理:
的初值分两种,是否强制第一个数做高度,分别用于处理答案只用 的数和用了 的数。
设 为只用 的数,用了 个,总高度为 的方案数,用于将 和 合并。其值为 。(不强制第一个数做高度的初值)
在做完 段整体+1之前,先令 。
数组开不下,需要滚动优化。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
ll read()
{
ll x = 0; bool f = false; char c = getchar();
while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return f ? -x : x;
}
const ll mod = 998244353;
const int N = 2005, M = 50, t = 45;
ll dp1[2][M][N], dp2[2][M][N], dp3[N][N], ans[N][N];
void add(ll &x, ll y){ x = (x + y >= mod) ? (x + y - mod) : (x + y); }
void init2()
{
dp2[0][1][t + 1] = 1;
for(int x = 0, i = 1; i <= 2000; ++i, x ^= 1)
{
for(int j = 0; j <= t; ++j)
for(int k = 0; k <= 2000; ++k)
dp2[x ^ 1][j][k] = 0;
for(int k = (t + 1); k <= 2000; ++k)
add(dp2[x][1][k], dp3[i - 1][k - (t + 1)]);
for(int j = 1; j <= t; ++j)
for(int k = j * (t + 1); k + j <= 2000; ++k)
add(dp2[x][j][k + j], dp2[x][j][k]);
for(int j = 1; j <= t; ++j)
for(int k = j * (t + 1); k <= 2000; ++k)
add(ans[i][k], dp2[x][j][k]);
for(int j = 1; j <= t; ++j)
for(int k = j * (t + 1); k <= 2000; ++k)
{
add(dp2[x ^ 1][j][k], dp2[x][j][k]);
if(k + t + 1 <= 2000) add(dp2[x ^ 1][j + 1][k + t + 1], dp2[x][j][k]);
}
}
}
void init()
{
for(int i = 1; i <= t; ++i) dp1[0][i][i] = 1;
for(int x = 0, i = 1; i <= 2000; ++i, x ^= 1)
{
for(int j = 0; j <= t; ++j)
for(int k = 0; k <= 2000; ++k)
dp1[x ^ 1][j][k] = 0;
for(int j = t - 1; j >= 1; --j)
for(int k = 0; k <= 2000; ++k)
add(dp1[x][j][k], dp1[x][j + 1][k]);
for(int j = 1; j <= t; ++j)
for(int k = 0; k <= 2000; ++k)
{
add(dp1[x ^ 1][j][k], dp1[x][j][k]);
if(j + k <= 2000) add(dp1[x ^ 1][j][k + j], dp1[x][j][k]);
}
for(int k = 1; k <= 2000; ++k) ans[i][k] = dp1[x][1][k];
}
for(int i = 0; i <= t; ++i)
for(int j = 0; j <= 2000; ++j)
dp1[0][i][j] = dp1[1][i][j] = 0;
for(int i = 1; i <= t; ++i) dp1[0][i][i] = dp1[0][i][0] = 1;
for(int x = 0, i = 1; i <= 2000; ++i, x ^= 1)
{
for(int j = 0; j <= t; ++j)
for(int k = 0; k <= 2000; ++k)
dp1[x ^ 1][j][k] = 0;
for(int j = t - 1; j >= 1; --j)
for(int k = 0; k <= 2000; ++k)
add(dp1[x][j][k], dp1[x][j + 1][k]);
for(int j = 1; j <= t; ++j)
for(int k = 0; k <= 2000; ++k)
{
add(dp1[x ^ 1][j][k], dp1[x][j][k]);
if(j + k <= 2000) add(dp1[x ^ 1][j][k + j], dp1[x][j][k]);
}
for(int k = 0; k <= 2000; ++k) dp3[i][k] = dp1[x][1][k];
}
// int flag = 0;
// for(int i = 0; i <= 2000; ++i)
// for(int j = 0; j <= 2000; ++j)
// flag += (dp3[i][j] != 0);
// printf("flag = %d\n", flag);
init2();
}
void solve()
{
int n = read(), m = read();
printf("%lld\n", ans[n][m]);
}
signed main()
{
init();
int T = read();
while(T--) solve();
return 0;
}
物竞大神gym一眼切了并用高超的编程能力A了这题。
详见此题解:博客园
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】