2024 1 月杂题选讲

2024 1 月杂题选讲

[集训队互测 2023] 基础寄术练习题

\(k=1\) 的情况,如果已经确定了这个序列 \(a\),则该式意义为,将总共 \(n\) 种颜色的球,第 \(i\) 种颜色是 \(a_i\),有 \(a_i\) 个,这 \(\sum a_i\) 个球排成一排,第一个球是 \(a_n\) 种颜色,第二种出现的颜色的球是第 \(a_{n-1}\) 种颜色,第 \(i\) 种球是 \(a_{n-i+1}\) 种颜色的概率,再除以 \(\frac{1}{\prod\limits_{i=1}^{n}a_i}\)。在 \(k=1\) 的时候,所有排列的答案之和为 \(\frac{1}{\prod\limits_{i=1}^{n}a_i}\),在 \(k=2\) 的时候,枚举 \(a_1\) 是什么,然后计算满足上面条件的排列方案数。考虑容斥,枚举一个集合在 \(a_1\) 后面出现,直接 dp 即可。

signed main()
{
    cin >> n >> m >> k >> mod;
	
	inv[1] = 1;
	
	for (rint i = 2; i <= N; i++)
	{
		inv[i] = (mod - mod / i) * inv[mod % i] % mod;
	}
	
	if (k == 1)
	{
		g[0] = 1;
		for (rint i = 1; i <= m; i++)
		{
			for (rint j = i - 1; ~j; j--)
			{
		   	 	g[j + 1] += inv[i] * g[j] % mod;
				g[j + 1] %= mod;		
			}	
		}
		cout << g[n] << endl;		
		return 0;
	}

  	f[0][0][0][0] = 1;
  	for (rint i = 1, p = 1, q = 0; i <= m; s += i, i++, p ^= 1, q ^= 1)
  	{
  		for (rint j = 0; j < i; j++)
  		{
  			for (rint k = 0; k <= s; k++)
  			{
  			    for (rint l = 0; l <= 1; l++)
  				{
  					int w = f[q][j][k][l];
  					f[q][j][k][l] = 0;
  					
  					f[p][j][k][l] += w;
  					f[p][j][k][l] %= mod;
  					
  					f[p][j + 1][k][l] += w * inv[i] % mod;
  					f[p][j + 1][k][l] %= mod;
  					
  					f[p][j + 1][k + i][l] += (mod - w) * inv[i] % mod;
  					f[p][j + 1][k + i][l] %= mod;
  					
  					if (!l) 
  					{
  						f[p][j + 1][k + i][1] += w * i % mod;
  						f[p][j + 1][k + i][1] %= mod;
  					}
  				}	
  			}
  		}		
  	}
  
  	for (rint i = 0; i <= s; i++)
  	{
  		ans += f[m & 1][n][i][1] * inv[i] % mod;
  		ans %= mod;
  	}
  	
  	cout << ans << endl;  
	
	return 0;
}

[国家集训队] Crash 的文明世界

考虑树形 dp 的 up and down trick,求这个柿子

\(\sum_{j=1}^ndist(i,j)^k\)

考虑树形 \(dp\),在第一遍 \(up\) 的时候,我们设 \(f[i][k]\)\(\sum_{j\in{i}}dist(i,j)^k\)

\(j\in{i}\) 表示 \(j\)\(i\) 子树内部

\(f[i][k]=\sum_{fa[j]=i}\sum_{t\in{j}}(dist(t,j)+1)^k\)

二项式定理展开

\(f[i][k]=\sum_{fa[j]=i}\sum_{t\in{j}}\sum_{r=0}^kC_{k}^{r}*dist(t,j)^r\)

\(f[i][k]=\sum_{fa[j]=i}\sum_{r=0}^kC_{k}^{r}*\sum_{t\in{j}}dist(t,j)^r\)

\(f[i][k]=\sum_{fa[j]=i}\sum_{r=0}^kC_{k}^{r}*f[j][r]\)

\(down\) 的方程式:

\(down\) 的时候 \(f[i][k]\) 表示是整棵树,到达 \(i\) 首先要到达 \(fa[i]\),于是就有 \(f[i][k]+=\sum_{j\notin{i},j\in{fa[i]}}(dist(fa[i],j)+1)^k\)

\(f[i][k]+=\sum_{j\notin{i},j\in{fa[i]}}\sum_{r=0}^kC_{k}^r*dist(fa[i],j)^r\)

考虑继续优化,发现求 \(x^k\) 可以理解为把 \(k\) 个物品放到 \(x\) 个互不相同的盒子里,允许有盒子空着不放的方案数,于是写成 \(x^k=\sum_{i=1}^kS(k,i)*C_{x}^i*i!\) 其中 \(S(k,i)\) 是第二类斯特林数,表示的是将 \(k\) 个球分到 \(i\) 个盒子里,这 \(i\) 个盒子没有差别,而且没有盒子是空的的方案数。

继续化柿子

\(ans[t]=\sum_{j=1}^{n}dist(t,j)^k\)

\(=\sum_{j=1}^{n}\sum_{i=1}^kS(k,i)*\binom{dist(t,j)}{i}*i!\)

\(=\sum_{i=1}^{k}S(k,i)*i!*\sum_{j=1}^n\binom{dist(t,j)}{i}\)

原问题转化为求出 \(\sum_{j=1}^n\binom{dist(t,j)}{i}\)

\(\sum_{j=1}^n\binom{dist(t,j)}{i}=\sum_{j=1}^n\binom{dist(t,j)-1}{i-1}+\sum_{j=1}^n\binom{dist(t,j)-1}{i}\)

再用类似于上方想法的树形 dp 转移一下就可以了。

void dfs1(int x)
{
	f[x][0] = 1;
	for (rint i = h[x]; i; i = ne[i])
	{
		int y = e[i];
		if (!d[y])
		{
			d[y] = d[x] + 1;
			dfs1(y);
			f[x][0] += f[y][0];
			for (rint j = 1; j <= m; j++)
			{
				f[x][j] = (f[x][j] + f[y][j - 1] + f[y][j]) % mod;
			} 
		}	
	}

}

void dfs2(int x)
{
	for (rint i = h[x]; i; i = ne[i])
	{
		int y = e[i];
		if (d[y] > d[x])
		{
			memset(a, 0, sizeof a);
			a[0] = f[x][0] - f[y][0];
			
			for (rint j = 1; j <= m; j++)
			{
				a[j] = (f[x][j] - f[y][j] - f[y][j - 1] + mod) % mod;
			}
		            
			f[y][0] += a[0];
			
			for (rint j = 1; j <= m; j++)
			{
				f[y][j] = (f[y][j] + a[j] + a[j - 1]) % mod;
			}
			dfs2(y);	
		}
	}
}

signed main()
{
	cin >> n >> m;
	
	for (rint i = 1; i < n; i++)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
		add(b, a);
	}
	
	s[0][0] = 1;
	
	for(rint i = 1; i <= m; i++)
	{
		s[i][1] = 1;
		s[i][i] = 1;
	}
	
	for (rint i = 1; i <= m; i++)
	{
		for (rint j = 1; j < i; j++)
		{
			s[i][j] = (s[i - 1][j - 1] + s[i - 1][j] * j) % mod;
		}
	}
			
	fac[0] = 1;
	for (rint i = 1; i <= m; i++)
	{
		fac[i] = (fac[i - 1] * i) % mod;		
	}
	d[1] = 1;
	
	dfs1(1);
	dfs2(1);
	
	for (rint i = 1; i <= n; i++)
	{
		for (rint j = 1; j <= m; j++)
		{
			f[i][j] = (f[i][j] % mod + mod) % mod;
		}
	}
	
	for (rint i = 1; i <= n; i++)
	{
		int ans = 0;
		for (rint j = 1; j <= m; j++)
		{
			ans = (ans + s[m][j] * fac[j] % mod * f[i][j]) % mod;
		}
		cout << ans << endl;
	}
	return 0;
}

[CTSC2007] 挂缀

很简单的一个反悔贪心,考虑何时为最优序列,显然有 \(C_i+W_i<C_j+W _j\)

后面的操作将已选的珠子放入一个优先队列,如果当前珠子不够加入,还比队首轻就那就将队首珠子删去。

实现的话怎么实现都行。

[WC2021] 表达式求值

做的时候人是麻的,就知道要中缀转后缀,然后就不知道怎么写了,后来学习了一下 wrpwrp 大佬的题解。

首先每一位是独立的, 互相之间不影响, 所以可以把每一位拆开考虑。

发现每次你只要考虑 \(10\) 个数, 但是 \(dp\) 复杂度依赖于操作串的长度, 考虑优化,发现所有操作都是最小最大, 只和大小有关而和具体值是多少关系不大。

枚举一个数当做答案, 然后大于等于这个数的设为 \(1\), 小于的设为 \(0\)。然后 \(dp\) 一遍求出最后结果为 \(1\) 的方案数, 就是最后的结果大于等于当前枚举的值的结果。简单差分即可得到答案。同时由于转化成为了\(01\) 序列, 就只有 \(2^{10}\) 种情况了, 预处理一下就好了。

int built(int l, int r) 
{
    if (l == r) 
	{
        ++idx;
        val[idx] = s[l] - '0';
        return idx;
    }
    if (p[r] == l) return built(l + 1, r - 1);
    int id = ++idx, pos = p[r] - 1;
    son[0][id] = built(l, pos - 1), son[1][id] = built(pos + 1, r);
    if (s[pos] == '?') val[id] = 10;
    else if (s[pos] == '<') val[id] = 11;
    else val[id] = 12;
    return id;
}

void dp(int x) 
{
    if (val[x] < 10) 
	{
        f[x][(k >> val[x]) & 1] = 1;
        return ;
    }
    dp(son[0][x]);
	dp(son[1][x]);
    for (rint i = 0; i <= 1; i++)
    {
        if (f[son[0][x]][i])
        {
            for (rint j = 0; j < 2; j++)
            {
                if (f[son[1][x]][j]) 
				{
                    int tmp = f[son[0][x]][i] * f[son[1][x]][j] % M;
                    if (val[x] == 10)
                    {
	                    (f[x][i] += tmp) %= M;
						(f[x][j] += tmp) %= M;					
					}
                    if (val[x] == 11)
                    {
                        (f[x][min((int)i, (int)j)] += tmp) %= M;						
					}
                    if (val[x] == 12)
                    {
                        (f[x][max((int)i, (int)j)] += tmp) %= M;						
					}
                }				
			}
		}	
	}
}

signed main() 
{
    cin >> n >> m;
    for (rint i = 0; i < m; i++)
    {
		for (rint j = 1; j <= n; j++)
		{
			cin >> a[i][j];
		}
	}
    scanf("%s", s + 1);
	int leng = strlen(s + 1);
    for (rint i = 1; i <= leng; i++) 
	{
        if (s[i] == '(') stk.push((int)i);
        if (s[i] == ')') p[i] = stk.top(), stk.pop();
        if ('0' <= s[i] && s[i] <= '9') p[i] = i;
    }
    root = built(1, leng);
    for (k = 0; k < (1 << m); k++) 
	{
        memset(f, 0, sizeof(f));
        dp(root);
        g[k] = f[root][1];
    }
    for (rint i = 1; i <= n; i++) 
	{
        vector<pair<int, int> > v;
        for (rint j = 0; j < m; j++) v.push_back({a[j][i], (int)j});
        sort(v.begin(), v.end());
        int now = (1 << m) - 1, j = 0;
        (ans += v[0].x * g[now] % M) %= M;
        while(j < m) 
		{
            int x = v[j].x;
            while (j < m && v[j].x == x)
            {
                now ^= (1 << v[j].y); 
				j++;				
			}
            if (now > 0)
            {
                (ans += g[now] * (v[j].x - x) % M) %= M;				
			}
        }
    }

    cout << ans << endl;
    
    return 0;
}

[CTSC2016] 单调上升序列

很有意思的一个构造题。

把边分成 \(\frac{n}{2}\) 个小组,对于同个中的边赋连续区间的值,限制最长单调上升序列不超过小组的数量。

现在,讲原问题转变为 :给出一张 \(n\) 个点的完全图,把边分为 \(n-1\) 组完美匹配

对于所有 \(0 ≤ x ≤ y ≤ n-1\),我们把 \((x,y)\) 这条边归入编号为 \(x+y\pmod{n-1}\) 的集合。处理所有连向 \(n-1\) 的边; 发现对于每个集合编号 \(i\),有且仅有一个 \(k\) 使得 \(2k=i\pmod{n-1}\),把 \(k\) 在当前集合里连向 \(n-1\) 即可。

signed main()
{
	cin >> n;
	for (rint i = 0; i <= n - 2; i++)
	{
		a[i] = i * n / 2;
	}
	
	for (rint i = 0; i <= n - 2; i++)
	{
		int k;
		for (rint j = i + 1; j <= n - 2; j++)
		{
			k = (i + j) % (n - 1);
			a[k]++;
			cout << a[k] << " ";
		}
		k = i * 2 % (n - 1);
		a[k]++;
		cout << a[k] << endl;
	}
	
    return 0;
} 

[CTS2023] 琪露诺的符卡交换

将题意转化为如何钦定每一排的顺序,使得每种颜色在每一列都出现过。

将每一排和它有的颜色连边,建出二分图,对于每一列跑一个二分图最大匹配,得出每一个颜色的位置。

对于两侧点集为 \(X, Y (|X| < |Y|)\) 的二分图, \(N(S)\) 表示点集 \(S\) 的邻域,若 \(\max_{S \in X}(|S| - N(S)) \leq 0\),那么存在完美匹配。

考虑在本题中,经过 \(i\) 轮删边后,每个点的度数为 \(n - i\),对于左侧任意一个大小为 \(x\) 的点集,向右侧连边数为 \((n - i)x\)。若邻域小于 \(x\),那么一定存在一个点的度数大于 \(n - i\),不符合题意,所以一定有完美匹配。

bool dfs(int x)
{
	if (v[x]) return 0;
	v[x] = 1;
	for (rint y = 1; y <= n; y++)
	{
		if (s[x][y] != 0 && (!fa[y] || dfs(fa[y])))
		{
			fa[y] = x; return 1;
		}		
	}
	return 0;
}

signed main()
{
	int T;
	cin >> T;
	while (T--)
	{
		cin >> n;
		for (rint i = 1; i <= n; i++)
		{
			for (rint j = 1; j <= n; j++)
			{
				cin >> a[i][j];
				s[i][a[i][j]]++;
			}
		}
		for (rint j = 1; j <= n; j++)
		{
			memset(fa, 0, n + 1 << 2);
			for (rint i = 1; i <= n; i++)
			{
				memset(v, 0, n + 1);
				dfs(i);
			}
			for (rint i = 1; i <= n; i++)
			{
				int k = 1;
				while (a[fa[i]][k] != i) k++;
				a[fa[i]][k] = 0;
				b[fa[i]][j] = k;
				s[fa[i]][i]--;
			}
		}

		int ans = n * (n - 1) >> 1;
		cout << ans << endl;
		
		for (rint i = 1; i <= n; i++)
		{
			for (rint j = 1; j < i; j++)
			{
				printf("%d %d %d %d\n", i, b[i][j], j, b[j][i]);
			}				
		}
	}
	
	return 0;
} 

[清华集训2017] 榕树之心

设一个 \(f_i\) 为从以 \(i\) 为根的子树开始移动最终能离 \(i\) 最近的距离

\(x\) 记为每个结点的重儿子

如果 \(f_x+1\le size_i-size_x-1\) ,从重儿子中移动,离重儿子最近的距离 \(+1\),即为离当前结点的距离,如果此距离小于其它可以移动的结点数量,则当 \(size_i-1\mod 2=0\) 时,则 \(f_i=0\) ,否则即 \(f_i=1\)
反之,则 \(f_i=f_x-size_i+size_x+2\)

考虑优化。遍历时维护一个 \(1\to x\) 缩点后的重儿子 \(son\), \(son=\max(son_x,son)\)

\(mson=x\) 时,用次重儿子进行判断。维护一个次重儿子,每个点的重儿子和次重儿子和 \(f\) 一样预处理好,当我们遍历向下一个结点拓展时讨论情况更新即可。

int cmp(int x, int y) 
{
    return size[x] > size[y] ? x : y;
}

void dfs1(int x, int father) 
{
    size[x] = 1;
	d[x] = d[father] + 1;
    for (rint i = h[x]; i; i = ne[i]) 
	{
        int y = e[i];
        if (y == father)
        {
			continue;
		}

        dfs1(y, x);
        size[x] += size[y];

        if (size[y] > size[son[x][1]]) 
		{
            son[x][2] = son[x][1];
            son[x][1] = y;
        } 
		else if (size[y] > size[son[x][2]]) 
		{
            son[x][2] = y;
        }
    }

    if (size[son[x][1]] - 2 * f[son[x][1]] <= size[x] - size[son[x][1]] - 1) 
	{
        f[x] = (size[x] - 1) / 2;
    } 
	else 
	{
        f[x] = size[x] - 1 - size[son[x][1]] + f[son[x][1]];
    }
}

void dfs2(int x, int father, int t) 
{
    int k = cmp(t, son[x][1]);

    if (size[k] - 2 * f[k] <= n - d[x] - size[k]) 
	{
        ans[x] = (n & 1) == (d[x] & 1);
    }

    for (rint i = h[x]; i; i = ne[i]) 
	{
        int y = e[i];
        if (y == father)
        {
			continue;
		}
        dfs2(y, x, y == son[x][1] ? cmp(son[x][2], t) : cmp(son[x][1], t));
    }
}

signed main() 
{
    cin >> w >> T;
    while (T--) 
	{
        idx = 0;m(f);m(h);m(e);m(ne);m(son);m(size);m(ans);
	    cin >> n;
	    for (rint i = 1; i < n; i++) 
		{
	        int a, b;
	        cin >> a >> b;
	        add(a, b);
	        add(b, a);
	    }
	
	    dfs1(1, 0);
		dfs2(1, 0, 0);
	
	    if (w == 3)
	    {
			cout << ans[1] << endl;
		}
	    else 
		{
	        for (rint i = 1; i <= n; i++)
	        {
				cout << ans[i];
			}
	        cout << endl;
	    }
    }

    return 0;
}

[清华集训2016] 如何优雅地求和

学习了一手 Binomial Sum 的微积分做法,很新颖。

\(\displaystyle\sum_{i=0}^m a_i[x^i]G(x)^k,k\in[1,n]\)

然后求 \(\displaystyle\sum_{i=0}^m a_i[x^i]F(G(x))\)

构造 \(G(x)=e^x,F(x)=(ax+1-a)^n\)

\((ax+1-a)F'(x)-anF(x)=0\)

\(G(0)=1\),设 \(\mathcal{F}(x)=F(x)\bmod(x-1)^{m+1}\),所以 \(\mathcal{F}(x+1)=F(x+1)\bmod x^{m+1}=(ax+1)^n \bmod x^{m+1}\)

变为 \((ax+1)F'(x+1)-anF(x+1)=0\)

考察 \((ax+1)\mathcal{F}'(x+1)-an\mathcal{F}(x+1)\) 其不合法的项,只有 \(\mathcal{F}'\) 从其原函数的 \(m+1\) 次跨向 \(m\) 次中来

所以 \((ax+1)\mathcal{F}'(x+1)\),具体地 \(1\mathcal{F}'(x+1)\) 应该获得贡献。但是实际上不存在。所以要在右边减去。

右边等于 \(-x^m[x^m]F'(x+1)=-na^{m+1}\binom{n-1}{m}x^m\)

发现右边是一个可以变化、展开的项,令 \(x-1\to x\)

然后得到 \((ax+1-a)\mathcal{F}'(x)-an\mathcal{F}(x)=-na^{m+1}\binom{n-1}{m}(x-1)^{m}\)

\([x^i]\mathcal{F}(x)=f_i\)\([x^i]\) 左式 \(=(1-a)(i+1)f_{i+1}+aif_i-anf_i\)

同时 \([x^i]\) 右式 \(=-na^{m+1}\binom{n-1}{m}\binom{m}{i}(-1)^{m-i}\)

由于两边多项式相等,系数也相等,左式等于右式,得出 \(f_i\) 递推式。

\(f_{m+1}=0\),倒着推更优。

卡老师还有一个神奇做法,见 link

int qpow(int a, int b)
{
    int s = 1;
    while (b)
	{
        if(b & 1) s = s * a % mod;  
        a = a * a % mod, b >>= 1;       
    }
    return s % mod;
}

int power(int x){return x % 2 ? mod - 1 : 1;}
int subm(int x){return (x % mod + mod) % mod;}
int C(int a, int b){return fac[a] * ifac[b] % mod * ifac[a - b] % mod;}

void init()
{
    fac[0] = 1;
    for (rint i = 1; i <= m; i++)
    {
        fac[i] = fac[i - 1] * i % mod;		
	}
    ifac[m] = qpow(fac[m], mod - 2);

    for (rint i = m - 1; i >= 0; i--)
    {
        ifac[i] = ifac[i + 1] * (i + 1) % mod;		
	}	
}

signed main() 
{
    cin >> n >> m >> a;

    for (rint i = 0; i <= m; i++)
    {
		cin >> g[i];
	}

    init();

    int res = 0;
    if (n <= m) 
	{
        for (rint i = 0; i <= n; i++) 
		{
            res += g[i] * C(n, i) % mod * qpow(a, i) % mod * qpow(mod + 1 - a, n - i) % mod;
            res %= mod;
        }
        cout << res << endl;
        return 0;
    }

    int k = ifac[m];
	int p = qpow(a, m + 1); 

    for (rint i = 1; i <= m; i++) 
	{
        k = k * (n - i) % mod;
    }
    f[m + 1] = 0;
    for (rint i = m; i >= 0; i--) 
	{
        f[i] = qpow(a * (n - i) % mod, mod - 2) * subm((i + 1) * (mod + 1 - a) % mod * f[i + 1] % mod - n * p % mod * k % mod * C(m, i) % mod * power(m - i + 1) % mod) % mod;
    }

    for (rint i = 0; i <= m; i++) 
	{
        res += g[i] * f[i] % mod;
        res %= mod;
    }

    cout << res << endl;
    
    return 0;
}

[清华集训2016] 组合数问题

卢卡斯的过程,实际上就是把最后一位数单独拿出来算,然后把最后一位去掉。把两个位置上的数都拆分的话,那么就相当于每一个位置上的组合数乘起来。
\(C_{a,b}=\prod_{i\leq a,j\leq b} C_{i,j}\)

原问题变为,有多少个 \(i\leq n,j\leq m\) 的数对 \((i,j)\),满足在 \(K\) 进制拆分下的某一位数 \(i < j\)

数位 dp 设 \(f_{i,01,01,01}\) 表示当前在 \(i\) 位,是否满足要求,\(i\) 的填法是否危险,\(j\) 的填法是否危险。记忆化转移即可。

int dfs(int pos, bool c, bool flag, bool t1, bool t2) 
{
    if (!pos) return c;
    auto &res = f[pos][c][flag][t1][t2];
    if (res != -1) return res;
    res = 0;
    int d = t1 ? a[pos] : k - 1, q = t2 ? b[pos] : k - 1;

    for (rint i = 0; i <= d; i++)
    {
        for (rint j = 0; (j <= i || flag) && j <= q; j++) 
		{
            bool a = c, b = flag;
            a |= (i < j), b |= (i != j);
            (res += dfs(pos - 1, a, b, t1 && i == d, t2 && j == q)) %= mod;
        }		
	}

    return res;
}

int solve(int n, int m) 
{
    int len = 0, lem = 0;
    memset(a, 0, sizeof a);
	memset(b, 0, sizeof b);
    memset(f, -1, sizeof f);

    while (n) a[++len] = n % k, n /= k;
    while (m) b[++lem] = m % k, m /= k;

    return dfs(max(len, lem), 0, 0, 1, 1);
}

signed main() 
{
    int T;
    cin >> T >> k;
    while (T--) 
	{
        int a, b;
        cin >> a >> b;
        cout << solve(a, b) << endl;
    }

    return 0;
}

[清华集训2015] 灯泡

简单数学题,也可以三分,做法很多,而且实际上不卡 \(long\) \(double\)

如果墙上有影子,那么 \(D+H-((H-h)/\tan \theta+D\tan \theta)\)

如果 \(\sqrt{(H-h)/D} \geq H/D\),答案为 \(hD/H\)

如果 \(\sqrt{(H-h)/D} \leq (H-h)/D\),答案为 \(h\)

否则答案为 \(D+H-2 \times \sqrt{(H-h) \times D}\)

[清华集训 2017] 福若格斯

可以通过这个题学习不平等博弈,由于整理时间较长,直接给自己留个链接算了,以后回来看看 Arahc's blog

int qpow(int a, int b)
{
    int s = 1;
    while (b)
	{
        if(b & 1) s = s * a % mod;  
        a = a * a % mod, b >>= 1;       
    }
    return s % mod;
}

int C(int x, int y) 
{
    if (y < 0 || x < y) return 0;
    return fac[x] * ifac[y] % mod * ifac[x - y] % mod;
}

int calc(int x, int y, int z) {return C(x + y, y + z);}

void solve() 
{
    int star = 0, up = 0, dwn = 0, c0 = 0, c1 = 0, f1 = 0, c2 = 0, f2 = 0, n;
    cin >> n;

    for (rint i = 1; i <= n; i++) 
	{
        int x;
		string s;
        cin >> s >> x;

        if (s == "LL_RR" || s == "LRL_R" || s == "L_RLR")  star += x;
        else if (s == "LRRL_" || s == "RLRL_" || s == "RRL_L") c1 += x;
        else if (s == "_RLRL" || s == "_RLLR" || s == "R_RLL") f1 += x;
        else if (s == "RL_LR") c2 += x;
        else if (s == "LR_RL") f2 += x;
        else if (s == "L_LRR") up += x;
        else if (s == "LLR_R") dwn += x;
        else c0 += x;
    }

    int wL = 0, wR = 0, wfir = 0, wsec = 0, w0 = 0;
    int all = qpow(2, c1 + f1);
    int sumL = 0, sumR = 0;

    for (rint i = -f1; i <= c1; i++)
    {
        pre[i + f1] = (i == -f1 ? 0 + calc(c1, f1, i) : pre[i - 1 + f1] + calc(c1, f1, i)) % mod;	
	}
    for (rint i = c1; i >= -f1; i--)
    {
        suf[i + f1] = (i == c1 ? 0 + calc(c1, f1, i) : suf[i + 1 + f1] + calc(c1, f1, i)) % mod;		
	}

    for (rint i = -f2; i <= c2; i++) 
	{
        int val = calc(c2, f2, i), c0 = 0;
        int pL = -i / 2, pR = -i / 2;

        while (pL * 2 + i <= 0) pL++;
        while ((pL - 1) * 2 + i > 0) pL--;
        while (pR * 2 + i >= 0) pR--;
        while ((pR + 1) * 2 + i < 0) pR++;
        sumL = pL > c1 ? 0 : suf[max(0ll, pL + f1)];
        sumR = pR < -f1 ? 0 : pre[min(c1 + f1, pR + f1)];
        if (i % 2 == 0) c0 = calc(c1, f1, -i / 2);
        if (!val) continue;
        wL = (wL + val * (sumL + mod)) % mod;
        wR = (wR + val * (sumR + mod)) % mod;
        w0 = (w0 + val * c0) % mod;
    }

    int sstar = (star == 0 ? 1 : qpow(2, star - 1));
    int suf = 0, pre = 0;

    for (rint i = up; i > 1; i--)
    {
        suf = (suf + calc(up, dwn, i)) % mod;		
	}

    for (rint i = -dwn; i < -1; i++)
    {
        pre = (pre + calc(up, dwn, i)) % mod;	
	}

    int g1 = calc(up, dwn, 1);
	int g_1 = calc(up, dwn, -1);
	int g0 = calc(up, dwn, 0);
    
	all = qpow(2, up + dwn + star);
    wL = (wL * all % mod + sstar * w0 % mod * (g1 + suf)) % mod;
    wR = (wR * all % mod + sstar * w0 % mod * (g_1 + pre)) % mod;
    wsec = (wsec + w0 * sstar % mod * g0) % mod;

    sstar = (star == 0 ? 0 : qpow(2, star - 1));
    wL = (wL + sstar * w0 % mod * suf) % mod;
    wR = (wR + sstar * w0 % mod * pre) % mod;
    wfir = (wfir + (g0 + g1 + g_1) % mod * sstar % mod * w0) % mod;

    wL = wL * qpow(2, c0) % mod;
    wR = wR * qpow(2, c0) % mod;
    wfir = wfir * qpow(2, c0) % mod;
    wsec = wsec * qpow(2, c0) % mod;

    wL = (wL + mod) % mod;
    wR = (wR + mod) % mod;
    wfir = (wfir + mod) % mod;
    wsec = (wsec + mod) % mod;
    cout << wL << " " << wR << " " << wfir << " " << wsec << endl;
}

signed main() 
{
    fac[0] = ifac[0] = 1;

    for (rint i = 1; i <= N; i++)
    {
        fac[i] = fac[i - 1] * i % mod;		
	}

    ifac[N] = qpow(fac[N], mod - 2);

    for (rint i = N - 1; i >= 1; i--)
    {
        ifac[i] = ifac[i + 1] * (i + 1) % mod;		
	}

    cin >> num >> T;

    while (T--)
    {
        solve();		
	}

    return 0;
}

CF1801E Gasoline prices

\(f_{i,j}\) 表示从点 \(i\) 向祖先方向的前 \(2^j\) 个点构成的链,\(g_{i,j}\) 表示从点 \(i\) 向祖先方向的前 \(2^j\) 个点构成的链翻转后的结果

\(j\) 层的并查集中包含 \(f_{k,j}\)\(g_{k,j}\) 中的所有元素,从 \(1\)\(2 n\) 编号。对于一个限制,求两条链上的点的值一一对应相等,在并查集中直接合并,以两条链的 \(LCA\) 为分界分成 \(3\) 部分,其中一部分是把一个 \(f_{k,k'}\) 和一个 \(g_{k,k'}\) 合并,还有两部分是把两个 \(f\) 合并。并查集中的每一层最多被合并 \(2n\) 次,所以总复杂度仍然是$ O(nlog^2n)$。

#include <bits/stdc++.h>
 
#define rint register int
#define int long long
#define endl '\n'
 
using namespace std;
 
const int N = 2e5 + 5;
const int M = 4e5 + 5;
const int mod = 1e9 + 7;
 
int n, q, fa[N][25], l[N], r[N];
int dep[N];
int ans = 1;
int h[N], e[N], ne[N], idx;
 
int qpow(int a, int b)
{
    int res = 1;
    while (b)
	{
        if (b & 1) res = res * a % mod;  
        a = a * a % mod;        
        b >>= 1;       
    }
    return res;
}
 
void add(int a, int b)
{
	e[++idx] = b, ne[idx] = h[a], h[a] = idx;
}
 
void dfs(int x, int depth) 
{
    dep[x] = depth;
    for (rint i = h[x]; i; i = ne[i])
    {
		int y = e[i];
		dfs(y, depth + 1);
	}
}
 
int lca(int x, int y) 
{
    for (rint i = 18; i >= 0; i--)
        if (fa[x][i] > 0 && dep[fa[x][i]] >= dep[y])
            x = fa[x][i];
    for (rint i = 18; i >= 0; i--)
        if (fa[y][i] > 0 && dep[fa[y][i]] >= dep[x])
            y = fa[y][i];
    if (x == y) return x;
    for (rint i = 18; i >= 0; i--)
        if (fa[x][i] != fa[y][i])
            x = fa[x][i], y = fa[y][i];
    return fa[x][0];
}
 
int get(int x, int y) 
{
    for (rint i = 0; i <= 18; i++) if (y & (1 << i)) x = fa[x][i];
    return x;
}
 
struct dsu 
{
    int fa[M];
    dsu() 
	{
        for (rint i = 0; i < M; i++) fa[i] = i;
    }
    int find(int x) 
    {
        return fa[x] == x ? x : fa[x] = find(fa[x]);
    }
    bool merge(int x, int y, int p) 
	{
        if (find(x) == find(y)) return 0;
        if (!p) 
		{
            ans *= qpow((r[fa[x]] - l[fa[x]] + 1) * (r[fa[y]] - l[fa[y]] + 1) % mod, mod - 2);
            ans %= mod;
            ans *= max(min(r[fa[x]], r[fa[y]]) - max(l[fa[x]], l[fa[y]]) + 1, 0ll);
            ans %= mod;
            l[fa[y]] = max(l[fa[y]], l[fa[x]]);
			r[fa[y]] = min(r[fa[y]], r[fa[x]]);
        }
        fa[find(x)] = find(y);
        return 1;
    }
} s[25];
 
void merge(int x, int y, int p) 
{
    if (!s[p].merge(x, y, p) || !p)return;
    merge(x, y, p - 1);
    merge(fa[x][p - 1], fa[y][p - 1], p - 1);
}
 
void merge_path(int x, int y, int p) 
{
    int v = __lg(p);
    merge(x, y, v);
    merge(get(x, p - (1 << v)), get(y, p - (1 << v)), v);
}
 
void mergeR(int x, int y, int p) 
{
    if (!s[p].merge(x, y + (p == 0 ? 0 : n), p) || !p) return;
    mergeR(x, fa[y][p - 1], p - 1);
    mergeR(fa[x][p - 1], y, p - 1);
}
 
void merge_pathR(int x, int y, int p) 
{
    int v = __lg(p);
    mergeR(x, get(y, p - (1 << v)), v);
    mergeR(get(x, p - (1 << v)), y, v);
}
 
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        x = x * 10 + ch - '0', ch = getchar();
    return x * f;
}
 
signed main() 
{
    cin.tie(0);
	cout.tie(0);
	
	cin >> n;
 
    for (rint i = 2; i <= n; i++)
    {
		fa[i][0] = read();
		add(fa[i][0], i);
	}
 
    for (rint i = 0; i < 19; i++) 
	{
		for (rint j = 1; j <= n; j++) 
		{
			fa[j][i + 1] = fa[fa[j][i]][i];
		}
	}
	
    for (rint i = 1; i <= n; i++)
	{
		l[i] = read();
		r[i] = read();
		ans *= (r[i] - l[i] + 1);
		ans %= mod;
	} 
	
    dfs(1, 0);
 
    cin >> q;
    while (q--) 
	{
        int a = read(), b = read(), c = read(), d = read();
 
        if (!ans) 
		{
            cout << 0 << endl;
            continue;
        }
 
        int p = lca(a, b);
        int q = lca(c, d);
 
        if (dep[a] - dep[p] < dep[c] - dep[q])
        {
            swap(a, c);
			swap(b, d);
			swap(p, q);			
		}
        
		int l1 = dep[c] - dep[q] + 1;
        int l2 = (dep[a] - dep[p]) - (dep[c] - dep[q]);
		int l3 = dep[a] + dep[b] - dep[p] * 2 + 1 - l1 - l2;
 
        if (l1) merge_path(a, c, l1);
        if (l2) merge_pathR(get(a, l1), get(d, l3), l2);
        if (l3) merge_path(b, d, l3);
 
        cout << ans << endl;
    }
 
    return 0;
}

[集训队互测 2023] 优惠购物

先想象这个题有没有一些性质,如果一次性使用了 \(c\) 个金币,那么在更前面使用更优。对于每个数最上面的 \(A_x\bmod c\) 个金币,优惠券越前面用越好。思路大概有了,应该是个思维题,纯贪心有可能莽过去。

从前往后考虑每个物品,并优先使用优惠券。反悔贪心,假设对 \(i\) 使用 \(C\_i\) 个优惠券,最上面 \(A_i\bmod c\) 个优惠券不会被用金币反悔,因为一个金币换一个优惠券不优。中间有若干段整段的 \(c\) 个优惠券,如果用 \(c\) 个金币反悔,会得到 \(c+1\) 个优惠券。最后有一个非整段的优惠券,同样的,如果用金币去完全兑换,可以多得到一张优惠券,否则只能得到金币对应数量的优惠券。

对于一个物品,如果有可以反悔的一段优惠券,肯定优先使用金币去反悔这段优惠券,而不是直接用金币去购买这段商品。考虑需要的优惠券不足以兑换一整段之前的优惠券的情况,这时需要考虑到底是将前面的一段优惠券兑换一部分,还是将金币留给自己,这个可以根据留给自己是否存在一张多余优惠券,以及两者的大小关系来判断。用堆实现,时间复杂度 \(O(n\log n)\)

#include <bits/stdc++.h>

#define rint register int
#define int long long
#define endl '\n'

#define x first
#define y second

using namespace std;

const int N = 1e6 + 5;

int n, m, c;
int a[N], b[N];

signed main() 
{
	cin.tie(0);
	cout.tie(0);    
    int T;
    cin >> T;
    
    while (T--)
    {
	    cin >> n >> m >> c;
	
	    for (rint i = 1; i <= n; i++) cin >> a[i];
	    for (rint i = 1; i <= n; i++) cin >> b[i];
	
	    priority_queue<pair<int, int> > q;
	    int ans = 0;
	    for (rint i = 1; i <= n; i++) 
		{
	        while (!q.empty() && b[i] > m) 
			{
	            auto p = q.top();
	            q.pop();
	            p.x *= -1; 
	            int w = (b[i] - m + 1) / (p.x + 1);
	            if (!w) 
				{
	                if (c - (a[i] - b[i]) % c < p.x && a[i] / c != (a[i] - b[i]) / c) 
					{
	                    q.emplace(-p.x, p.y);
	                    if (c - (a[i] - b[i]) % c <= b[i] - m)
						{
	                        int w = c - (a[i] - b[i]) % c;
	                        b[i] -= w;
	                        continue;
	                    }
	                    break;
	                }
	                ans += b[i] - m;
	                q.emplace(-(p.x - (b[i] - m)), 1);
	                p.y--;
	                if (p.y) q.emplace(-p.x, p.y);
	                m = b[i];
	                break;
	            }
	            if (w >= p.y) 
				{
	                ans += p.x * p.y;
	                m += (p.x + 1) * p.y;
	                continue;
	            }
	            ans += w * p.x;
	            m += w * (p.x + 1);
	            if (w ^ p.y) q.emplace(-p.x, p.y - w);
	        }
	        int w = min(b[i], m);
	        if (w <= a[i] % c) 
			{
	            ans += a[i] - w;
	            m -= w;
	            m += (a[i] - w) / c;
	        } 
			else 
			{
	            ans += a[i] - w;
	            m -= w;
	            m += (a[i] - w) / c;
	            w -= a[i] % c;
	
	            if (w % c) q.emplace(-(w % c), 1);
	            if (w / c) q.emplace(-c, w / c);
	        }
	    }
	    cout << ans << endl;	
	}
        
    return 0;    
}

[集训队互测 2023]Permutation Counting 2

钦定两维分别有 \(x,y\)<,最后的答案可以二项式反演得出。有 \(x,y\)< 等价于有 \(n-x,n-y\) 个连续段,转化成钦定有 \(x,y\) 个连续段。

考虑排列与其逆排列连续段之间的关系,发现当在放原排列的一个连续段的时候,其会在逆排列的若干个连续段最后加上若干个数。设 \(a_{i,j}\) 表示在放原排列第 \(i\) 个连续段的时候在逆排列第 \(j\) 个连续段最后放了 \(a_{i,j}\) 个数,则满足如下条件的 \(a\) 唯一对应一个排列:

  • \(\sum a_{i,j}=n\)
  • \(a\) 每行每列都有值。

前一个限制可以插板法做,后一个限制可以钦定某几行某几列为空,然后容斥,同样可以拆开成两维分别来一遍,时间复杂度 \(O(n^3)\)

int n, m, k, p;
int ans[N][N], c[N][N];
int fac[M], ifac[M];
int f[N][N];

int qpow(int a, int b)
{
	int res = 1;
	while (b)
	{
		if (b & 1) res = res * a % p;
		b >>= 1;
		a = a * a % p;
	}
	return res;
}

void init()
{
    fac[0] = ifac[0] = 1;
    for (rint i = 1; i < M; i ++)
    {
        fac[i] = fac[i - 1] * i % p;
        ifac[i] = ifac[i - 1] * qpow(i, p - 2) % p; 
    }	
}

int C(int a, int b)
{
	if (b < 0 || a < b) return 0;
	return fac[a] * ifac[a - b] % p * ifac[b] % p;
}

signed main() 
{
    cin >> n >> p;

    init();
    
    for (rint i = 0; i <= n; i++) 
	{
	    c[i][0] = 1;
		for (rint j = 1; j <= i; j++)
	    {
	        c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % p;			
		}
	}

    for (rint i = 1; i <= n; i++) 
	{
        for (rint j = 1; j <= n; j++)
        {
            f[i][j] = C(i * j + n - 1, i * j - 1);			
		}
    }

    for (rint i = 1; i <= n; i++) 
	{
        for (rint j = 1; j <= n; j++)
        {
            for (rint k = 1; k < i; k++)
            {
                f[i][j] = (f[i][j] + (p - f[k][j]) * c[i][k]) % p;					
			}	
		}
    }

    for (rint i = 1; i <= n; i++) 
	{
        for (rint j = 1; j <= n; j++)
        {
            for (rint k = 1; k < j; k++)
            {
                f[i][j] = (f[i][j] + (p - f[i][k]) * c[j][k]) % p;							
			}
		}
    }

    for (rint i = 1; i <= n; i++)
    {
        for (rint j = 1; j <= n; j++)
        {
            ans[n - i][n - j] = f[i][j];			
		}	
	}

    for (rint i = 0; i < n; i++) 
	{
        for (rint j = n - 1; ~j; j--)
        {
            for (rint k = j + 1; k < n; k++)
            {
                ans[i][j] = (ans[i][j] + (p - ans[i][k]) * c[k][j]) % p;				
			}			
		}
    }

    for (rint i = n - 1; ~i; i--) 
	{
        for (rint j = 0; j < n; j++)
        {
            for (rint k = i + 1; k < n; k++)
            {
                ans[i][j] = (ans[i][j] + (p - ans[k][j]) * c[k][i]) % p;					
			}	
		}
    }

    for (rint i = 0; i < n; i++)
    {
        for (rint j = 0; j < n; j++)
        {
            printf("%lld%c", ans[i][j], " \n"[j == n - 1]);				
		}
	}

    return 0;
}

[THUPC 2024 初赛] 排序大师

考虑在排列开头加上 \(0\),结尾加上 \(n+1\),然后建边 \(p_i+1\to p_{i+1}\),最终状态是 \(n+1\) 个环。

假设我们现在操作 \(i,j,k,l\),那么原来的边是 \(p_{i-1}+1\to p_{i}\)\(p_{j}+1\to p_{j+1}\)\(p_{k-1}+1\to p_k\)\(p_l+1\to p_{l+1}\)。 现在的边是 \(p_{i-1}+1\to p_{k},p_{j}+1\to p_{l+1},p_{k-1}+1\to p_i,p_{l}+1\to p_{j+1}\)。 这样的操作的任意性不会强于两次选择两个点,交换其出边。选择两个点交换出边只会增加至多 \(1\) 个环,所以,如果能每次减少两个环,那么这个交换次数就是顶到下界的。

构造方法:

  • 选择最小的点 \(x\),满足 \(x\) 前面存在一个 \(y>x\),选择这个 \(y\)
  • 容易说明,\(x-1\)\(y\) 前面,\(y+1\)\(x\) 后面,因此将 \(x\) 换到 \(x-1\) 前面,\(y\) 换到 \(y+1\) 后面即可增加两个自环。

时间复杂度 \(O(n^2)\)

int n, f[N], w[N];
vector<tuple<int, int, int, int> > ans;
void solve(int a, int b, int c, int d) 
{
    memset(w, 0, sizeof w);
    int idx = -1;
    for (rint i = 0; i < a; i++)          w[++idx] = f[i];
    for (rint i = c; i <= d; i++)         w[++idx] = f[i];
    for (rint i = b + 1; i < c; i++)      w[++idx] = f[i];
    for (rint i = a; i <= b; i++)         w[++idx] = f[i];
    for (rint i = d + 1; i <= n + 1; i++) w[++idx] = f[i];
    memcpy(f, w, sizeof f);
    ans.emplace_back(a, b, c, d);
}

signed main() 
{
    cin >> n;
    for (rint i = 1; i <= n; i++)
    {
		cin >> f[i];
	}
    f[0] = 0;
    f[n + 1] = n + 1;
    
    while (1) 
	{
        int maxx = 0, minn = inf;
        for (rint i = 1; i <= n; i++) 
		{
            maxx = max(maxx, f[i]);
            if (maxx > f[i]) minn = min(minn, f[i]);
        }
        if (minn == inf) break;
        int p = find(f + 1, f + n + 1, minn) - f;
		int q = max_element(f + 1, f + p + 1) - f;
        int k1 = find(f, f + n + 2, f[p] - 1) - f;
		int k2 = find(f, f + n + 2, f[q] + 1) - f;
        solve(k1 + 1, q, p, k2 - 1);
    }

    cout << ans.size() << endl;
    for (auto i : ans) 
	{
        int a, b, c, d;
        tie(a, b, c, d) = i;
        cout << a << " " << b << " " << c << " " << d << endl;
    }
    return 0;
}

CF1305G Kuroni and Antihype

将原问题转化,添加一个点权为 \(0\) 的点,将边权变为两端点点权和,将 \(ans\) 减去 \(\sum a_i\)。原题意的生成有向森林转化为了生成树。

从大到小枚举边权和 \(x\),枚举 \(x\) 的子集 \(i\),将点权为 \(i\)\(i \operatorname{bitxor} x\) 的点缩点并计算贡献。

有点卡常,但是不影响过。

const int N = 3e5 + 5;

int n, m;
int maxx;
int fa[N], cnt[N];
long long ans;

int find(int x) 
{
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

void merge(int a, int b, long long w) 
{
    if (cnt[a] && cnt[b]) 
	{
        int x = find(a);
		int y = find(b);
        if (x != y) 
		{
            ans += w * (cnt[x] + cnt[y] - 1);
            fa[x] = y;
			cnt[y] = 1;
        }
    }
}

signed main() 
{
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	
	cin >> n;
	cnt[0] = 1;

    for (rint i = 1; i <= n; i++) 
	{
        int x;
        cin >> x;
        cnt[x]++;
        ans -= x;
        maxx = max(maxx, x);
    }

    m = 1 << (32 - __builtin_clz(maxx));

    for (rint i = 0; i < m; i++)
    {
		fa[i] = i;
	}

    for (rint i = m - 1; ~i; i--) 
	{
        for (rint j = i; j; j = i & (j - 1))
        {
            if (cnt[j] && cnt[i ^ j])
            {
                merge(j, i ^ j, i);					
			}	
		}
        merge(i, 0, i);
    }

    cout << ans << endl;
    
    return 0;
}

[集训队互测 2023] Grievous Lady

由于最终结果选 \(a\) 的个数和选 \(b\) 的个数不会差很多,所以可以一开始的时候枚举 \(S\) 集合大小 \(cnt\)\([\frac{n}{2}-40,\frac{n}{2}+40]\) 之间。然后随机排列,将前 \(cnt\) 个选 \(a\),后 \(n-cnt\) 个选 \(b\),之后随机选数翻转,如果更优就翻。类似还有好多奇怪的随机化可以过。

当然,这道题还存在一种正解的 dp 做法,\(f_{i,j}\) 表示选到第 \(i\) 个点,\(a\) 的总和为 \(j\) 的最大 \(b\) 总和,之后再二维偏序,分析可以被扔掉的点可以做到只剩下 \(O(n)\) 个状态。但是这个写法虽然比较稳定,但是太难写了,比较麻烦。随机化的得分会在 \(98-100pts\) 间波动。但是随机种子给的好可以稳过。(直接 srand(time(0)) 过的概率也是很高的。

int n;
int A, B;
int a[N], b[N], p[N];
__int128 res;
bool v[N];

#define iint __int128
inline void write(iint n)
{
    if (n < 0)
	{
        putchar('-');
        n *= -1;
    }
    if (n > 9) write(n / 10);
    putchar(n % 10 + '0');
}
#undef iint

void solve() 
{
    __int128 l = 0, r = 0;
    
    for (rint i = 1; i <= n; i++)
    {
		if (!v[i]) l += a[i];
		else r += b[i];
	}
    __int128 now = l * r;
    int times = 4e4 + 5;
	while (times--) 
	{
        int x = rand() % n + 1;
        if (!v[x]) 
		{
            if (now < ((l - a[x]) * (r + b[x])))
            {
                v[x] ^= 1;
				l -= a[x]; 
				r += b[x]; 
				now = l * r;				
			}
        } 
		else 
		{
            if (now < (l + a[x]) * (r - b[x]))
            {
                v[x] ^= 1;
				l += a[x]; 
				r -= b[x];
				now = l * r;				
			} 
        }
    }
    res = max(res, now);
}

void work() 
{
    for (rint i = 1; i <= n; i++)
    {
		cin >> a[i] >> b[i];
		p[i] = i;
	}
    res = 0;
    int times = 30;
    while (times--)
	{
        random_shuffle(p + 1, p + n + 1);
        for (rint i = 1; i <= n / 2; i++)
        {
			v[p[i]] = 1;
		}
		for (rint i = n / 2 + 1; i <= n; i++)
		{
			v[p[i]] = 0;
		}
        solve();
    }
    write(res);
    cout << endl;
}

signed main() 
{
    srand(time(0));
    int T;
    cin >> T >> n >> A >> B;
    while (T--) work();
    return 0;
}
posted @ 2024-01-04 20:35  PassName  阅读(34)  评论(0编辑  收藏  举报