2024ICPC沈阳VP记录

补题链接

J 题

纯签到题,比较前四个队伍的最强队与后四个队伍的最强队,输出总决赛结果。

点击查看代码
#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;
}

D 题

交换一个序列的任意两个元素会改变序列逆序对数的奇偶性,对A交换或者对B交换都可以看作,将B映射成严格递增的排列后,对A中的两个元素交换。

因此操作次数的奇偶性就是逆序对数的奇偶性。

而将区间 [l,r] 左移 k 位可以看作 k(rl) 次交换,只需要关注这个数的奇偶性。

点击查看代码
#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;
}

E 题

逆向思考,问题转化为从 0000 状态出发,经过所需状态的最小代价。

原问题给的操作数可以看作9种位运算,且必有一种可以被其他操作代替,因此从 0000 到任意状态至多 8 次操作,暴搜出所有可能的操作序列,求出从 0000 到任意状态的最小代价,记为 sumi

状态数只有 216 种,转移只需要考虑相邻状态,设 dps,i 表示已经经过集合 s 中的状态,当前处于状态 i 所需的最小代价。

dpst,t=mindps,i+sumit

mns 为经过 s 集合的状态的最小代价。

mns=minstdpt,i

O(3n) 枚举子集即可。

点击查看代码
#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;
}

M 题

将搬房间转化成两个楼层中的有向边,问题转化为在一个有向图上,环的边权和不为 0,找出所有位于或者能够到达这样的环的点。

大于一个点的强联通分量一定是环,对于每个强联通分量,将边权取正取反两次判负环,但是SPFA被卡掉了。

特殊点在于只需要判断环的边权和是否为 0,意味着从 1n 的任意路径长度都与 n1 的任意路径长度加和为 0,这符合势能的定义。

对于每个强联通分量任选起点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;
}

B 题

结论:若 gcd(n,m)1,则无解,否则构造 ai=(1+im)modnmbi=(1+in)modnm

证明1:

不妨设 a1b10modnm,则必有 ua1,vb1,使得 uv=nm,则第一行至多有 nmu 个不同的数数,分别为 0,u,2u,nmu,但是不能少于 m 个,因此有 un,同理有 vm,因此有 u=n,v=m

此时第一行为 0,n,2n,(m1)n,第一列为 0,m,2m,(n1)m

要求其中只有 0 是相同的,需要满足 gcd(n,m)=1,因为此时对于 xn=ym 的最小正整数解为 x=m,y=n

证明2:

aibjaibjmodnm,整理为 m(jj)n(ii)modnm

由于 gcd(m,n)=1,i,in,所以只有 i=i,j=j,因此得证按照此构造,网格中没有两个相同的数。

点击查看代码
#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;
}

G 题

题解移步至 2024ICPC沈阳VP - lyrrr


I 题

首先求出每个点到根节点的距离 disi

题目中可以将树枝修改成任意长度,所以可以取一些很大的数,使得整个子树内的 dis 都不会和其他点再相同,这种操作类似于切掉子树

考虑 u,v 以及他们的最近公共祖先 x,其中 disu=disv,此时必须修改 uv 的路径上任意一个树枝,对应着切掉一个子树,显然被切掉的子树越大越好,此时只有两种选择:切掉 x 的左子树或者右子树。

因此从下往上考虑每一层的某个点是否需要切掉某个子树,由于至多切 n 次,所以总的情况数为 2n,用 map 或者 set 暴力维护某个点子树内的所有 dis 值,不断向上合并。

复杂度大概是 4n×()=O()

点击查看代码
#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;
}

H 题

图中有两棵树,不妨设节点1所在树为A树,另一棵树为B树。

对树DFS遍历,恰好经过一条从A树走向B树的边,令这条边为 pq

还可以向图中加入其他边,保证不改变DFS遍历顺序。

DFS遍历导致只能加返租边,对于一条边 uvu 可以向 v 子树中所有大于 v 的点连边。

将可以加入的边分为4类:

  1. A 树内部的边,可以用线段树合并预处理。(与 p,q 的选择无关)
  2. B 树内部的边,由于根变化,需要换根DP+DFS序上主席树预处理。(与 p 的选择无关)
  3. 1p 的路径上的点(不包括 p)与 B 树的点的连边,沿用2的主席树预处理。(与 q 的选择无关)
  4. pB 树的点的连边,与 q 的选择有关,可以预处理 sumi 表示 B 树中大于等于 i 的点的个数。

对于这些点,可选可不选,方案数为 2k

特别注意的是,当 A 树的大小为 n1 时,可以没有 pq,也就是只需要 A 树内部连边。

更多细节请看代码

点击查看代码
#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;
}

A 题

经典做法之看到 =k 考虑根号分治。

t=45m,考虑对于 ditdi>t 分别 DP

dp1i,j,k 表示前 i 个数中,最小的数大于等于 j,总高度为 k 的方案数。其中 jt

转移时先做后缀和:

dp1i,j,k+=dp1i,j+1,k

转移:

dp1i+1,j,k=dp1i,j,k

dp1i+1,j,k+j=dp1i,j,k

观察这个转移可以发现这个 dp 的定义可以修改成,后一个数填 j 的方案数。

dp2i,j,k 表示前 i 个数中,有 j 个贡献了高度,总高度为 k 的方案数。

转移时考虑费用提前计算,能够做贡献的数最小为 t+1

转移时先做如下处理:

dp2i,j,k+j+=dp2i,j,k

已有的段整体+1。

转移:

dp2i+1,j,k=dp2i,j,k

dp2i+1,j+1,k+t+1=dp2i,j,k

以下是一些细节处理:

dp1 的初值分两种,是否强制第一个数做高度,分别用于处理答案只用 t 的数和用了 t+1 的数。

dp3i,j 为只用 t 的数,用了 i 个,总高度为 j 的方案数,用于将 dp1dp2 合并。其值为 dp1i,1,j。(不强制第一个数做高度的初值)

在做完 dp2 段整体+1之前,先令 dp2i,1,k+=dp3i1,kt1

数组开不下,需要滚动优化。

点击查看代码
#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;
}

K 题

物竞大神gym一眼切了并用高超的编程能力A了这题。

详见此题解:博客园

posted @   梨愁浅浅  阅读(51)  评论(7编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示