ICPC2025 网络赛第二场

source

赛后总结

我们队伍这次算式考得比上次好一些了吧,由于过去了太久了,也记不太清具体的情况了。

说实话,三个人对于我个人的思考的专注度还是有很大的考验的。如何在一个专心思考和平衡队友之间找到最合适的点也是一个很大的挑战吧

总算不是我自己一个人孤军奋战了,yjx 同学也做了两道题

好好读题吧,题面显然是可以交流的,然后比赛中间犯下的低级错误也很唐诗,之后的比赛一定要按照之后的方法进行改正

然后是 jzr 同学做的找规律题确实有一点点太久了,写代码也有一点久,确实还需要很多训练的提升

赛后提升

增强码力,补知识点

提交之前:

  • long long
  • 数据范围
  • 函数返回值
  • n/m
  • 修改之后过一遍样例再交
  • 交之前从头到尾自己给自己理一遍代码逻辑

做过的每一道题目进行生词查询

A. Angry Birds

D. Arcane Behemoths

我们发现一个子序列里面第 \(k\) 小的数对这个子序列的贡献次数为 \(\max\{1,2^{k-2}\}\)

所以在原序列中第 \(k\) 小的数的贡献次数为 \((1 + \sum_{i=1}^{k-1} \binom {k-1} i 2^i)\times 2^(n-k)\)

其中 \(\sum_{i=1}^{k-1} \binom {k-1} i 2^i\) 可以通过二项式定理化简为 \(3^{k-1}\)

code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 998244353,NN = 2e5 + 8;
int T;
ll n,a[NN],pw2[NN],pw3[NN];
ll inv2;
void solve(){
	ll ans = 0;
	cin >> n;
	for(int i = 1; i <= n; ++i)
		cin >> a[i];
	sort(a+1,a+1+n);
	for(int i = 1; i <= n; ++i){
		ans += a[i] * pw2[n-i] % MOD * (pw3[i-1]+1) % MOD * inv2 % MOD;
		if(ans >= MOD) ans -= MOD;
	}
	cout << ans << '\n';
}
inline ll ksm(ll x,ll k){
	ll res = 1;
	while(k){
		if(k & 1) res = res * x % MOD;
		x = x * x % MOD;
		k >>= 1;
	}
	return res;
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0);
	pw2[0] = pw3[0] = 1;
	inv2 = ksm(2,MOD-2);
	for(int i = 1; i <= 2e5; ++i){
		pw3[i] = pw3[i-1] * 3 % MOD;
		pw2[i] = pw2[i-1] * 2 % MOD;
	}
	cin >> T;
	while(T--){
		solve();
	}
}

E. Zero

似乎赛时队友在 \(E\) 题找规律上花了很久很久

赛时code
/*#include<bits/stdc++.h>
using namespace std;
#define int long long
int dfs(int n, int m, int x, int lst) {
	if(n == 0) return x == 0;
	int ans = 0;
	for(int i = 0; i < (1 << m); i++) {
		if(i == lst) continue;
		ans += dfs(n - 1, m, x ^ i, i);
	}
	return ans;
}
signed main() {
	int ans;
	for(int n = 1; n <= 12; n++) {
		printf("%d : ", n);
		for(int m = 1; m <= 3; m++) {
			ans = dfs(n, m, 0, -1);
			printf("%lld : (", ans);
			for(int i = 2; i * i <= ans; i++) {
				while(ans % i == 0) {
					ans /= i;
					printf("%lld ", i);
				}
			}
			if(ans != 1) printf("%lld", ans);
			printf(") ");
		}
		printf("\n");
	}
	return 0;
}*/
#include<bits/stdc++.h>
using namespace std;
const int mod = 998244353;
long long ksm(long long d, long long z) {
	long long ans = 1;
	for(long long i = 1; i <= z; i <<= 1, d = d * d % mod) {
		if(z & i) ans = ans * d % mod;
	}
	return ans;
}
struct Matrix{
	long long a[2][2];
	Matrix(){
		memset(a,0,sizeof(a));
	}
	void init(){
		for(int i = 0; i <= 1; ++i) a[i][i] = 1;
	}
	Matrix operator *(Matrix Y){
		Matrix res;
		for(int i = 0; i <= 1; ++i){
			for(int j = 0; j <= 1; ++j){
				for(int k = 0; k <= 1; ++k){
					res.a[i][j] += a[i][k] * Y.a[k][j];
					res.a[i][j] %= mod;
				}
			}
		}
		return res;
	}
	void print(){
		for(int i = 0; i <= 1; ++i){
			for(int j = 0; j <= 1; ++j){
				printf("%d ",a[i][j]);
			}
			puts("");
		}
		puts("--------");
	}
};
Matrix ksm(Matrix x,long long k){
	Matrix ans;
	ans.init();
	while(k){
		if(k&1) ans = ans * x;
		x = x * x;
		k >>= 1;
	}
	return ans;
}
long long sb(int n,int m){
//	printf("opps:%d %d\n",n,m);
	if(n == 0) return 1;
	Matrix ans;ans.a[0][0] = 1;ans.a[0][1] = mod - 1; 
	Matrix a1;
	a1.a[0][0] = ksm(2,m) - 1;
	a1.a[1][0] = 1; a1.a[1][1] = mod - 1;
//	(ans* ksm(a1,n)).print();
	return (ans * ksm(a1,n)).a[0][0];
}
int main() {
	int T, n, m;
	scanf("%d", &T);
	while(T--) {
		scanf("%d%d", &n, &m);
		if(n & 1) printf("%lld\n", ksm(ksm(2, m) - 1, n - 1));
		else {
			if(n == 2) printf("0\n");
			else if(m == 1) {
				if(n & 2) printf("0\n");
				else printf("2\n");
			}
			else printf("%lld\n", ksm(2, m) * ksm(ksm(2, m) - 1, n >> 1) % mod * sb((n >> 1) - 2, m) % mod);
		}
	}
	return 0;
}

我们现在考虑正解怎么做。

因为题目信息和相邻两个数有关,所以我们把确定原序列转化为确定第一个数和差分序列

我们首先枚举原序列的差分数组 \(\{d_i|d_i = a_{i+1} - a_i\}\),显然原序列的差分数组应该是每一位都可以是 \([1,2^m)\)

case 1

如果 \(n\) 为奇数,那么我们的异或和为 \(0\) 的要求就如下:

\[a_1 \oplus(a_1\oplus d_1) \oplus(a_1\oplus d_1\oplus d_2) \oplus \dots = 0 \]

即:

\[ a_1 = d_2\oplus d_4 \oplus d_6\cdots \]

方案数即为 \((2^m-1)^{n-1}\)

case 2

为了避免重复,下记题面中的 \(m\)\(M\)

如果 \(n\) 为偶数,也可以同理得到下面的式子:

\[0 = d_1\oplus d_3\oplus d_5\oplus \dots \oplus d_{n-1} \]

我们设 \(f(n)\) 表示 \(n\) 个非零数异或和为 \(0\) 的方案数,\(g(n)\) 表示 \(n\) 个数异或和为 \(0\) 的方案数(易知 \(g(n) = 2^{M(n-1)},g(0) = 1\))

\[g(n) = \sum_{m = 0} ^ n \binom n m f(m) \]

二项式反演得到:

\[\begin{aligned} f(n) = & \sum_{m = 0} ^ n (-1)^{n-m} \binom n m g(m) \\ = & (-1)^n + \sum_{m = 1} ^ n (-1)^{n-m} \binom n m 2^{M(m-1)} \\ = & (-1)^n - (-1)^n 2^{-M} + \sum_{m = 0} ^ n (-1)^{n-m} \binom n m 2^{M(m-1)} \\ = & (-1)^n - (-1)^n 2^{-M} + 2^{-M}\sum_{m = 0} ^ n (-1)^{n-m} \binom n m (2^M)^m \\ = & (-1)^n - (-1)^n 2^{-M} + 2^{-M} (2^M-1)^n \end{aligned} \]

我们的答案求解由三部分乘积组成:

  • \(a_1\) 任选:\(2^M\)
  • \(d_1,d_3,\dots\) 在值域 \([1,2^M)\) 任选:\((2^M-1)^{\frac n / 2 - 1}\)
  • \(d_2,d_4,\dots\): \(f(\frac n 2)\)

答案即为:

\[(2^M-1)^{n-1} + (1-2^M)^{\frac n 2} \]

I. DAG Query

我们可以发现,这个图给出来时没有作用的

对于一个长度为 \(k\) 的路径,经过了 \(\times c\) 的操作之后,它的价值会乘以 \(c^k\)

所以我们的目标就是求出多项式 \(a_1x^1 + a_2x^2 + a_3x^3 + \dots + a_{n-1}x^{n-1}\) 中的所有系数

在考场上我想到可以使用使用高斯消元(想高斯消元想疯了)

高斯消元版
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll NN = 1e3 + 8,MOD = 998244353;
int n,m;
ll a[NN][NN];

inline ll ksm(ll x,ll k){
	ll res = 1;
	while(k){
		if(k & 1) res = res * x % MOD;
		x = x * x % MOD;
		k >>= 1;
	}
	return res;
}
inline ll inv(ll x){
	return ksm(x,MOD - 2);
}
int main(){
//	ios::sync_with_stdio(false),cin.tie(0);
	cin >> n >> m;
	if(n == 1){
		cout << "!" << endl;
		int k; cin >> k;
		cout << 0 << endl;
		return 0;
	}
	for(int i = 1,x,y; i <= m; ++i){
		cin >> x >> y;
	}
	for(int i = 1,x; i <= n-1; ++i){
		cout << "? 1 " << n << " " << i << endl;
		cin >> a[i][n];
		for(ll j = 1,pw = i; j < n; ++j,pw = pw*i%MOD){
			a[i][j] = pw;
		}
	}
//	for(int i = 1; i <= n-1; ++i){
//		for(int j = 1; j <= n; ++j){
//			cout << a[i][j] << " ";
//		}
//		cout << endl;
//	}
	for(int i = 1; i <= n-1; ++i){
		ll mdf = inv(a[i][i]);
		for(int k = i; k <= n; ++k)
			a[i][k] = a[i][k] * mdf % MOD;
		for(int j = i+1; j <= n-1; ++j){
			mdf = a[j][i];
			for(int k = i; k <= n; ++k){
				a[j][k] = ((a[j][k] - mdf * a[i][k]) % MOD + MOD) % MOD;
			}
		}
	}
//	for(int i = 1; i <= n-1; ++i){
//		for(int j = 1; j <= n; ++j){
//			cout << a[i][j] << " ";
//		}
//		cout << endl;
//	}
	for(int i = n-1; i >= 1; --i){
		for(int j = 1; j < i; ++j){
			a[j][n] = ((a[j][n] - a[j][i] * a[i][n]) % MOD + MOD) % MOD;
		}
	}
//	for(int i = 1; i <= n-1; ++i){
//		for(int j = 1; j <= n; ++j){
//			cout << a[i][j] << " ";
//		}
//		cout << endl;
//	}
	cout << "!" << endl;
	ll k;
	cin >> k;
	ll ans = 0;
	for(int i = 1; i <= n-1; ++i){
		ans = (ans + ksm(k,i) * a[i][n])% MOD;
	}
	cout << ans << endl;
}

然而,考完之后发现好唐,题目只会询问一次,可以用拉格朗日插值

然后我们可以让询问的下标连续以做到 \(O(n)\) 查询

拉差
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll NN = 1e3 + 8,MOD = 998244353;
int n,m;
ll y[NN],jc[NN];
ll pre[NN],suf[NN];
ll ksm(ll x,ll k){
	ll ans = 1;
	while(k){
		if(k & 1) ans = ans * x % MOD;
		x = x * x % MOD;
		k >>= 1;
	}
	return ans;
}
void query(int m){
	pre[0] = 1;suf[n+1] = 1;
	for(int i = 1; i <= n; ++i){
		pre[i] = pre[i-1] * (m - i + 1) % MOD;
		suf[n-i+1] = suf[n-i+2] * (m-n+i) % MOD;
	}
	ll ans = 0;
	for(int i = 1; i <= n; ++i){
		ll fz = pre[i-1] * suf[i+1] % MOD, fm = ((n-i)%2?-1:1) * jc[i-1] % MOD * jc[n-i] % MOD;
		fm = ksm(fm,MOD - 2);
		ans += y[i] * fz % MOD * fm % MOD;
		ans %= MOD;
	}
	if(ans < 0) ans += MOD;
	cout << ans << endl;
}
int main(){
	cin >> n >> m;
	//预处理阶乘 
	jc[0] = 1;
	for(int i = 1; i <= n; ++i) jc[i] = jc[i-1] * i % MOD;
	//输入图 
	for(int i = 1,x,y; i <= m; ++i){
		cin >> x >> y;
	}
	//询问得到所有点值 
	for(int i = 2; i <= n; ++i){
		cout << "? 1 " << n << " " << i-1 << endl;
		cin >> y[i];
	}
	
	cout << "!" << endl;
	cin >> m;
	query(m); 
}
posted @ 2025-09-19 22:26  ricky_lin  阅读(179)  评论(0)    收藏  举报