2024牛客暑期多校训练营2

写在前面

比赛地址:https://ac.nowcoder.com/acm/contest/81597

以下按个人向难度排序。

比第一场好点但还是唐氏。

E

签到。

dztlb 大神写的,我看都没看。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int t,n;
signed main(){
	cin>>t;
	while(t--){
		cin>>n;	
		int cnt=0;
		for(int i=60;i>=0;i--){
			if(n&(1ll<<i)){
				++cnt;
			}
		}
		if(cnt==1){
			cout<<-1<<'\n';
		}else{
			int ans=0;
			int tmp=0;
			for(int i=60;i>=0;i--){
			if(n&(1ll<<i)){
				ans+=(1ll<<i);
				++tmp;
				if(tmp==cnt-1) break;
			}
		}
			cout<<ans<<'\n';
		}
	}
	
	return 0;
}

C

贪心,模拟。

刚看完题面还以为要写什么牛逼图论算法发现就两行那纯傻逼题了。

考虑仅让这个人从左往右走,每列仅有两个格子,于是仅需讨论从上一列先移动到哪个格子即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pii std::pair<int,int>
#define mp std::make_pair
const int kN = 1e6 + 10;
//=============================================================
int n, ans, f[2][kN];
std::string s[2];
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n;
  std::cin >> s[0] >> s[1];
  f[0][0] = (s[0][0] == 'R'), f[1][0] = (s[1][0] == 'R');
  if (f[0][0] && f[1][0]) f[0][0] = f[1][0] = 2;

  for (int j = 1; j < n; ++ j) {
    if (s[0][j] == 'R') f[0][j] = 1 + f[0][j - 1];
    if (s[1][j] == 'R') f[1][j] = 1 + f[1][j - 1];
    if (s[0][j] == 'R' && s[1][j] == 'R') {
      if (f[0][j] < f[1][j]) f[0][j] = f[1][j] + 1;
      else if (f[0][j] > f[1][j]) f[1][j] = f[0][j] + 1;
      else ++ f[0][j], ++ f[1][j];
    }
  }
  for (int j = 0; j < n; ++ j) ans = std::max(ans, std::max(f[0][j], f[1][j]));
  if (!ans) std::cout << 0;
  else std::cout << ans - 1 << "\n";
  return 0;
}

H

dztlb 大神写的。

发现给定区间代表的路径仅需经过 \((x, y)\) 即有贡献,于是考虑枚举区间左端点 \(l\),仅需找到最靠小的右端点 \(r\) 令区间 \([l, r]\) 代表的路径恰好停在 \((x, y)\) 则所有大于 \(r\) 的右端点均有贡献。

考虑将区间拆成两后缀之差,则仅需使用对每种后缀和,维护对应的最靠左的后缀位置,枚举左端点时仅需查询最近的后缀 \([r + 1, n]\)\([l, n]\)\((x, y)\) 之差恰好为 \([r + 1, n]\) 即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int t,n,x,y;
char s[N];
int a[N],b[N],ans;
int sa[N],sb[N];
map<int,int>ma;
int id(int x,int y){
	return (x+100000)*100000+y;
}
signed main(){
	cin>>n>>x>>y;
	scanf("%s",s+1);
	if(x==0&&y==0){
		cout<<(n+1)*n/2<<'\n'; return 0;
	}
	for(int i=1;i<=n;++i){
		if(s[i]=='D'){
			a[i]=1;
		}
		if(s[i]=='A'){
			a[i]=-1;
		}
		if(s[i]=='S'){
			b[i]=-1;
		}
		if(s[i]=='W'){
			b[i]=1;
		}
	}
	ma[id(0,0)]=n+1;
	for(int i=n;i>=1;--i){
		sa[i]=sa[i+1]+a[i];
		sb[i]=sb[i+1]+b[i];
	}
	for(int i=n;i>=1;--i){
		int wa=sa[i]-x,wb=sb[i]-y;
		if(ma[id(wa,wb)])ans+=(n+1-ma[id(wa,wb)]+1);
		ma[id(sa[i],sb[i])]=i;
	}
	cout<<ans;
	return 0;
}
/*
7 -3 0
AAAADAD
*/

I

线性 DP。

并非区间 DP!虽然确实是维护区间的贡献,但并非区间 DP!

如果你和我一开始一样设了状态 \(f_{l, r}\) 表示区间 \([l, r]\) 的贡献之和并尝试使用转移 \(f_{l, r} = \max(f_{l + 1, r}, f_{l, r - 1})\),你将发现这种转移将无法抉择同时有两个区间 \([l, r - a], [l + b, r]\) 产生贡献时哪种更优。因为不仅需要考虑子区间获得的价值之和,还要考虑操作之后还剩多少牌以便之后操作。这很难办!!!于是换个思路:

发现每种权值 \(i\) 唯一地对应一个区间 \([l_i, r_i]\),且最终答案中选出的有贡献的若干区间,要么互不相交,要么被另一个区间完整包含,不可能出现区间的部分交叉。

考虑在原数列两侧添加 0,则仅需考虑完整地对某个区间进行操作将其全部删去可获得的最大价值即可。于是记 \(f_{i} (0\le i\le n)\) 表示最后进行的一次操作为 \([l_i, r_i]\) 使,使用区间 \([l_i, r_i]\) 可获得的最大价值之和,答案即为 \(f_0\)

发现操作顺序按照权值递减是最优的:不相交的区间操作顺序无影响,被层层相互包含的区间先对内层的权值较大的区间操作可获得更多的价值。于是考虑按照权值 \(i\) 递减转移 \(f_i\)。考虑当前转移 \(f_i\),初始化 \(f_{i} = i\times (r_i - l_i + 1)\),考虑在此之前进行了哪些操作:相当于从区间 \([l_i + 1, r_i - 1]\) 中选出若干互不相交的区间 \([l_j, r_j]\),每个区间的贡献为 \(f_{j} - (r_j - l_j + 1)\times i\),表示在对 \([l_i + 1, r_i - 1]\) 进行操作前对 \([l_j, r_j]\) 进行操作可获得的价值减去对此次操作的影响(减少的卡牌数)。

这是一个经典的带权线段覆盖问题,即从数条带权线段中选择若干互不相交的线段使他们的权值之和最大。设 \(g_{k}(l_i + 1\le k\le r_i - 1)\) 表示使用右端点不大于 \(k\) 的线段可获得的最大权值之和,初始化 \(g_{l_i} = 0\),则有转移:

\[\begin{aligned} g_{k - 1}&\rightarrow g_{i}\\ g_{l_{a_k} - 1} + f_{a_k} - (r_{a_k} - l_{a_k} + 1)\times i &\rightarrow g_{k} &(k = r_{a_k}\land a_k > i \land l_{i} < l_{a_k}) \end{aligned}\]

则有:

\[f_{i} = 2\times i + g_{r_i - 1} \]

答案即为 \(f_{0}\)

上述过程中需要枚举 \(n\) 种权值,每种权值的转移过程均为 \(O(n)\) 级别,则总时间复杂度 \(O(n^2)\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kN = 3e3 + 10;
//=============================================================
int n, a[kN << 1], rval[kN << 1], lpos[kN << 1], rpos[kN << 1];
LL f[kN << 1], g[kN << 1];
//=============================================================
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n;
  for (int i = 1; i <= 2 * n; ++ i) {
    std::cin >> a[i];
    if (lpos[a[i]] == 0) lpos[a[i]] = i;
    else rpos[a[i]] = i, rval[i] = a[i];
  }
  lpos[0] = 0, rpos[0] = 2 * n + 1;

  for (int i = n; i >= 0; -- i) {
    int l = lpos[i], r = rpos[i];
    f[i] = 1ll * i * (r - l + 1);
    g[l] = f[i] - 2ll * i;

    for (int j = l + 1; j < r; ++ j) {
      g[j] = g[j - 1];

      int v = rval[j], l1 = lpos[v];
      LL d = f[v] - 1ll * i * (j - l1 + 1);
      if (v > i && l1 > l && g[j] < g[l1 - 1] + d) {
        g[j] = g[l1 - 1] + d;
      }
      f[i] = std::max(f[i], g[j] + 1ll * i * 2ll);
    }
  }
  std::cout << f[0];
  return 0;
}
/*
5
1 2 4 5 4 3 5 3 2 1
*/

B

根号分治,启发式,乱搞

一个超级超级好写的纯启发式的不在题解里的大概是 \(O(m\log m + \left(\sum_i k_i\right)^{\frac{3}{2}}\log{\sqrt{\sum_i k_i}})\) 的很烂的乱搞做法,但是能卡过去,鉴定为为了把各种根号乱搞放过去数据不敢造太强。

由于 \(\sum k_i\le 10^5\),所有询问的点数之和不太多,反过来考虑,则所有点所在的询问数量之和也为 \(\sum k_i\le 10^5\),于是使用 set 记录每个点所在的询问集合。

考虑对原图进行 Kruscal,在此过程中考虑一条边对所有询问的影响:

首先有 \(\sum k_i\le 10^5\),则可以对每个询问的点集均维护一个并查集,空间复杂度完全允许,偷懒可以直接用 map;又发现一条边 \((u, v, w)\) 对某个询问有影响,当且仅当 \(u, v\) 均在该询问中,考虑对询问集合较小的一方枚举询问 \(q\),并检查 \(q\) 是否在另一集合中,若在则这条边对该询问有影响,大力维护并查集即可。

这东西看起来是 \(O(mq\log q\log n)\) 的,但是钦定了每次枚举询问时均枚举的较小集合(不钦定铁 TLE),枚举集合元素并完成维护操作单次复杂度为 \(O(\log |S|)\),则在无重边无自环的无向简单图上,枚举询问的总数并不会超过 \(O\left(m\frac{\sum_i k_i}{n}\right)\le O(\left(\sum_i k_i\right)^{\frac{3}{2}})\) 级别,枚举的总复杂度不会超过:

\[O\left(m\frac{\sum_i k_i}{n}\log \left(\frac{\sum_i k_i}{n}\right)\right)\le O\left(\left(\sum_i k_i\right)^{\frac{3}{2}}\log{\sqrt{\sum_i k_i}}\right) \]

\(m = \left(\sum_i k_i\right) = n^2\),实际跑起来效率还是挺高的。

则总时间复杂度 \(O(m\log m + \left(\sum_i k_i\right)^{\frac{3}{2}}\log{\sqrt{\sum_i k_i}})\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n, m, q;
struct Edge {
  int u, v, w;
} e[kN];
int sz[kN], tot[kN];
LL ans[kN];
std::vector<int> querynode[kN];
std::set<int> inquery[kN];
std::map<int, int> idinquery[kN];
int sumsz[kN], fa[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); 
  return f * w;
}
bool cmp(Edge fir_, Edge sec_) {
  return fir_.w < sec_.w;
}
int faid(int query_, int node_) {
  return sumsz[query_ - 1] + idinquery[query_][node_];
}
int Find(int x_) {
  return fa[x_] == x_ ? x_ : fa[x_] = Find(fa[x_]);
}
void Merge(int x_, int y_) {
  int fax = Find(x_), fay = Find(y_);
  if (fax == fay) return ;
  fa[fax] = fay;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  n = read(), m = read(), q = read();
  for (int i = 1; i <= m; ++ i) {
    int u_ = read(), v_ = read(), w_ = read();
    e[i] = (Edge) {u_, v_, w_};
  }
  std::sort(e + 1, e + m + 1, cmp);

  for (int i = 1; i <= q; ++ i) {
    sz[i] = read(), sumsz[i] = sumsz[i - 1] + sz[i];
    for (int j = 1; j <= sz[i]; ++ j) {
      int p = read();
      inquery[p].insert(i);
      idinquery[i][p] = j;
      fa[faid(i, p)] = faid(i, p);
    }
  }

  for (int i = 1; i <= m; ++ i) {
    int u_ = e[i].u, v_ = e[i].v, w_ = e[i].w;
    if (inquery[u_].size() > inquery[v_].size()) std::swap(u_, v_);
    for (auto qid: inquery[u_]) {
      if (!inquery[v_].count(qid)) continue;
      int faidu = faid(qid, u_), faidv = faid(qid, v_);
      if (Find(faidu) == Find(faidv)) continue;
      Merge(faidu, faidv);
      ++ tot[qid];
      ans[qid] += w_;
    }
  }
  for (int i = 1; i <= q; ++ i) {
    if (tot[i] != sz[i] - 1) printf("-1\n");
    else printf("%lld\n", ans[i]);
  }
  return 0;
}

A

构造。

妈的场上过了 98% 的数据,又是什么神仙数据把我们卡掉了!!!哦原来是 1 5 5 13 3 3 B 这组数据呃呃呃呃!!!看到这组数据瞬间就知道错哪了呃呃呃呃!!!

下面是场上做法,虽然思想是对的但是讨论过多有点太复杂了,建议参考其他比较简单的题解。

首先考虑可构造出的曲线数量的上下界。一个显然的想法是每块砖与其他砖尽可能连通时曲线最少,则下界在所有砖均相同时取到,此时有 \(k=n+m\);尽可能形成闭合图形从而减少与其他砖的连通时曲线最多。考虑到最小的闭合图形是使用形如 \(\small\begin{bmatrix} A B\\ B A \end{bmatrix}\) 的四块相邻的砖组成的圆,于是仅需考虑构造的圆的数量即可,手玩下发现每多一个圆曲线数就会多 1。上界在所有相邻的砖种类不同时取到,模拟一下即可计算出上界。

基于上述上下界考虑如何构造。考虑先将整个矩阵构造成两种取到上界的形式,先判断 \(k\) 是否在界内,然后从一个角开始不断进行单个位置的修改,每次修改均可删去一个圆,也即减少一条曲线,直到删到 \(k\) 为止,此时检查指定位置是否为指定砖块即可。

细节还有点多的呃呃:

  • 为了防止指定砖块被删掉,考虑到构造出的矩阵是有对称性的,于是需要同时维护从对称的两个角开始删的情况。
  • 删除的顺序要注意一下,否则会被某些正好限定了中心位置的数据卡掉,比如:
    1
    5 5 13
    3 3 B
    
#include<bits/stdc++.h>
using namespace std;
#define int long long
int t;
int n,m,k,x,y;
char w;
char c[805][805];
void print(){
	cout<<"Yes\n";
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			cout<<c[i][j];
		} cout<<'\n';
	}
}
void printo(){
	cout<<"Yes\n";
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			cout<<c[n-i+1][m-j+1];
		} cout<<'\n';
	}
}
signed main(){
    // freopen("1.txt", "r", stdin);

	cin>>t;
	while(t--){
		cin>>n>>m>>k;
		cin>>x>>y>>w;
		if(k<n+m){
			cout<<"No\n";
			continue;
		}
		int cnt=0;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				c[i][j]='A';
			}
		}
		for(int i=1;i<=n;++i){
			if(i%2==1){
				for(int j=2;j<=m;j+=2){
					c[i][j]='B';
				}
			}else{
				for(int j=1;j<=m;j+=2){
					c[i][j]='B';
				}
			}
		}
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				if(i+1>n) continue;
				if(j+1>m) continue;
				if(c[i][j]=='A'&&c[i+1][j]=='B'&&c[i][j+1]=='B'&&c[i+1][j+1]=='A') ++cnt;
			}
		}
		if(k>n+m+cnt){//
			cout<<"No\n";
			continue;
		}
		int tmp=n+m+cnt;
		if(tmp==k){
			if(c[x][y]==w){
				print();
				continue;
			}
			if(c[n-x+1][m-y+1]==w){
				printo();
				continue;
			}
		}
		bool fl=0;
		for(int i=n;i>=2;i--){
			if(fl) break;
			if(tmp<k) break;
			for(int j=m;j>=2;--j){
				if(c[i][j]=='A'){
					c[i][j]='B';
					--tmp;
					if(tmp==k){
						if(c[x][y]==w){
							print();
							fl=1; break;
						}
						if(c[n-x+1][m-y+1]==w){
							printo();
							fl=1; break;
						}
						break;
					}
				}
			}
		}
		if(fl) continue;
		
		
		
		
		
		
		
		
		
		
		
		cnt=0;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				c[i][j]='B';
			}
		}
		for(int i=1;i<=n;++i){
			if(i%2==1){
				for(int j=2;j<=m;j+=2){
					c[i][j]='A';
				}
			}else{
				for(int j=1;j<=m;j+=2){
					c[i][j]='A';
				}
			}
		}
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				if(i+1>n) continue;
				if(j+1>m) continue;
				if(c[i][j]=='A'&&c[i+1][j]=='B'&&c[i][j+1]=='B'&&c[i+1][j+1]=='A') ++cnt;
			}
		}
		if(k>n+m+cnt){
			cout<<"No\n";
			continue;
		}
		tmp=n+m+cnt;
		if(tmp==k){
			if(c[x][y]==w){
				print();
				continue;
			}
			if(c[n-x+1][m-y+1]==w){
				printo();
				continue;
			}
		}
		fl=0;
		for(int i=n;i>=2;i--){
			if(fl) break;
			if(tmp<k) break;
			for(int j=1;j <= m-1;++j){
				if(c[i][j]=='B'){
					c[i][j]='A';
					--tmp;
					if(tmp==k){
						if(c[x][y]==w){
							print();
							fl=1; break;
						}
						if(c[n-x+1][m-y+1]==w){
							printo();
							fl=1; break;
						}
					}
				}
			}
		}
		if(fl) continue;
		cout<<"No\n";
	}
	return 0;
}
/*
1
5 5 13
3 3 B
*/

G

数学,背包 DP。

场上两个脑瘫用 \(2\sqrt{1000} \approx 200\) 算出来 1000 内有 200 个质数之后就做不下去了,口胡了一个和 \(\sqrt{1000}\) 内质数个数有关的状压 DP 发现铁过不去就跑路了,未曾想 \(\sqrt{1000}\) 内仅有 11 个质数 \(\{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31\}\)——我草那这不做完了!!!!

贡献为平方数,套路地考虑分解质因数,检查质因数的次数是否为偶数,于是考虑选出若干数,使它们乘积中质因数次数均为偶数的各种方案的贡献之和。限定了 \(a_i\le 1000\),每个数至多仅有一个 \(>\sqrt{1000} = 32\) 的质因数,且这些质因数一定是成对儿存在的,可以考虑直接匹配;又发现 \(\sqrt{1000}\) 内的质数仅有 11 个,可以状压存下,于是考虑 DP。

每个数在原数列中的位置是不重要的,于是考虑转换下形式方便转移。对所有数 \(a_i\) 做下质因数分解,记大于 \(\sqrt{1000}\) 的质因数为 \(p_i\),出现次数为奇数的 11 个小质因数集合为 \(S_i\),记 \(C_i = \prod_{p' | a_i} p'^{\left\lfloor\frac{c}{2}\right\rfloor}\) 为选择该数的贡献,转化为三元组 \((p_i, S_i, C_i)\) 的形式。

在有贡献的子序列中,含有大于 \(\sqrt{1000}\) 的质因数 \(p\) 的数 \((p, S_i, C_i)\) 一定是成对存在的,于是考虑按照 \(p\) 升序枚举所有数进行选择,并考虑每次选出偶数个 \(p_i\) 相同的数。记状态 \(f_{p, j, s, 0/1}\) 表示当前选择到大于 \(\sqrt{1000}\) 的质因数 \(p\),选择的数小于 \(\sqrt{1000}\) 的出现次数为奇数的质因数集合为 \(s\),选择到第 \(j\) 个含有 \(p\),且已经选择了偶数/奇数个含有 \(p\) 的数 \((p, S_i, C_i)\) 时,可以获得的贡献之和。

首先枚举不存在大于 \(\sqrt{1000}\) 的质因数 \(p\) 的数(即有 \(p_i=1\))对 \(f_{1, 0, s, 0}\) 进行初始化。然后考虑含有 \(p\) 的数集的 \((S_i, C_i)\) 组成的集合为 \((S'_1,C'_1), (S'_2, C'_2)\cdots, (S'_k, C'_k)\),则有转移:

\[\begin{aligned} &\sum_{p'<p} f_{p', 0, s, 0} \rightarrow f_{p, 0, s, 0}\\ \forall 1\le j\le k, 0\le s< 2^{11}, \ &\begin{cases} f_{p, j - 1, s, 0} \rightarrow f_{p, j, s, 0}\\ f_{p, j - 1, s, 1} \rightarrow f_{p, j, s, 1}\\ f_{p, j - 1, s, 0}\times C_j \rightarrow f_{p, j, s \oplus S_j, j, 1}\\ f_{p, j - 1, s, 1}\times C_j\times p \rightarrow f_{p, j, s \oplus S_j, j, 0} \end{cases} \end{aligned}\]

答案即为:

\[f_{\max p, 0, 0, 0} \]

上述前两维状态都可以滚动数组一下,本质上就是一个对集合状态进行的背包,总时间复杂度 \(O(2^{11}\times n)\) 级别。

详见代码。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kS = (1 << 11);
const LL p = 1e9 + 7;
//=============================================================
int n;
int prime[11] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};
std::vector<pr <int,int> > b[kS];
LL f[2][kS][2];
//=============================================================
void init() {
  std::cin >> n;
  for (int i = 1; i <= n; ++ i) {
    int a, s = 0, c = 1; std::cin >> a;
    for (int j = 0; j < 11; ++ j) {
      while (a % prime[j] == 0) {
        if ((s >> j) & 1) c *= prime[j];
        a /= prime[j], s ^= (1 << j);
      }
    }
    b[a].push_back(mp(s, c));
    // std::cout << a << " " << s << " " << c << "\n";
  }
}
LL calc(int s1_, int s2_) {
  LL ret = 1;
  for (int i = 0; i < 11; ++ i) {
    if (((s1_ & s2_) >> i) & 1) ret *= prime[i], ret %= p; 
  }
  return ret;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  init();

  int now = 0; 
  f[0][0][0] = 1;
  for (auto [s, c]: b[1]) {
    for (int i = 0; i < kS; ++ i) f[now ^ 1][i][0] = f[now][i][0];
    for (int i = 0; i < kS; ++ i) {
      f[now ^ 1][i ^ s][0] += f[now][i][0] * c % p * calc(i, s) % p;
      f[now ^ 1][i ^ s][0] %= p;  
    }
    now ^= 1;
  }
  for (int val = 32; val <= 1000; ++ val) {
    if (b[val].empty()) continue;
    for (int i = 0; i < kS; ++ i) f[now][i][1] = 0;
    for (auto [s, c]: b[val]) {
      for (int i = 0; i < kS; ++ i) f[now ^ 1][i][0] = f[now][i][0], f[now ^ 1][i][1] = f[now][i][1];
      for (int i = 0; i < kS; ++ i) {
        f[now ^ 1][i ^ s][0] += f[now][i][1] * c % p * calc(i, s) % p * val % p; 
        f[now ^ 1][i ^ s][1] += f[now][i][0] * c % p * calc(i, s) % p;
        f[now ^ 1][i ^ s][0] %= p, f[now ^ 1][i ^ s][1] %= p;
      }
      now ^= 1;
    }
  }
  std::cout << (f[now][0][0] - 1 + p) % p << "\n";
  return 0;
}

写在最后

学到了什么:

  • B:自然根号,大力冲一发!
  • I:发现 DP 在转移时无法从多种子问题中选到最优解,说明状态有误,或者不是 DP。
posted @ 2024-07-21 04:20  Luckyblock  阅读(184)  评论(0编辑  收藏  举报