「杂题选讲」#2

菜死了啊。

都是些比较简单的题目,可惜我做不出来

ABC261D Flipping and Bonus

分析

称正面朝上为获胜,反面朝上为失败。

一开始认为,输掉一局相当于把计数器置 \(0\),所以应该加入状态,设 \(f(i,j)\) 为前 \(i\) 局,输掉 \(j\) 局,所能得到的最大收益。这个状态最大的问题是信息缺失。你怎么知道你赢下第 \(i\) 局是几连胜?无法转移。

\(f(i,j)\) 为前 \(i\) 局,其中第 \(i\) 局为 \(j\) 连胜。特别地,当 \(j=0\) 时表示输掉第 \(i\) 局。相比于上一个状态,这个状态是能够进行转移的,且没有荣誉信息,是正确的状态。

转移就比较简单了 \[ f(i,j) = \begin{cases} f(i-1,j-1) + x_i + b_j & j > 0 \\ \max_{k \in [0,i-1]}{\{ f(i-1,k) \}} & j=0 \end{cases} \] 最终答案 \[ \max_{i \in [1,n]} {\{ f(n,i) \}} \] 最后一局输掉显然不划算啊。

CODE

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5005;
int n, m, x[N], b[N], f[N][N];
int read() {
	int a=0, f=1; char c=getchar();
	while(!isdigit(c)) {
		if(c=='-') f=-1;
		c=getchar();
	}
	while(isdigit(c)) a=a*10+c-'0', c=getchar();
	return a*f;
}
signed main() {
	n=read(), m=read();
	for(int i=1;i<=n;++i) x[i]=read();
	for(int i=1;i<=m;++i) {
		int c=read(), y=read();
		b[c]=y;
	}
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=i;++j) f[i][j]=f[i-1][j-1]+x[i]+b[j];
		f[i][0]=0;
		for(int j=0;j<i;++j) f[i][0]=max(f[i][0],f[i-1][j]);
	}
	int ans=0;
	for(int i=1;i<=n;++i) ans=max(ans,f[n][i]);
	printf("%lld\n",ans);
}

ABC261D Sorting Color Balls

分析

首先明确冒泡排序的交换次数就是逆序对的个数。

注意到相同颜色的球,交换它们是不耗费代价的。

很容易想到答案即为总的逆序对个数减去同种颜色的逆序对个数。

CODE

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3e5+5;
int n, ans, a[N], c[N];
vector<int> p[N];
void modify(int x,int y) { for(;x<=3e5;x+=x&-x) c[x]+=y; }
int query(int x) {
	int ans=0;
	for(;x;x-=x&-x) ans+=c[x];
	return ans;
}
int read() {
	int a=0, f=1; char c=getchar();
	while(!isdigit(c)) {
		if(c=='-') f=-1;
		c=getchar();
	}
	while(isdigit(c)) a=a*10+c-'0', c=getchar();
	return a*f;
}
signed main() {
	n=read();
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=1;i<=n;++i) {
		int x=read();
		p[0].push_back(x);
		p[a[i]].push_back(x);
	}
	for(int i=0;i<=n;++i) {
		int sz=p[i].size();
		for(auto x:p[i]) {
			int dlt=query(n)-query(x);
            // 逆序对
			if(!i) ans+=dlt; else ans-=dlt;
            // i=0为原序列,否则为颜色i
			modify(x,1);
		}
		for(auto x:p[i]) modify(x,-1);
        // 删除
	}
	printf("%lld\n",ans);
}

ABC262C Min Max Pair

分析

注意到当 \(a_i = i\) 与任何 \(a_j = j\),其中 \(i < j\),配对时都满足条件。设满足 \(a_i = i\)\(i\) 的个数为 \(t\),则答案一定含有 \(\frac{t(t+1)}{2}\)

否则若 \(a_i = j\)\(j > i\),那么 \(a_j\) 必须为 \(i\) 才能满足条件。扫一遍即可。

CODE

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
#define int long long
int n, ans, t, a[N];
int read() {
	int a=0, f=1; char c=getchar();
	while(!isdigit(c)) {
		if(c=='-') f=-1;
		c=getchar();
	}
	while(isdigit(c)) a=a*10+c-'0', c=getchar();
	return a*f;
}
signed main() {
	n=read();
	for(int i=1;i<=n;++i) {
		a[i]=read();
		if(a[i]==i) ++t;
	}
	ans=t*(t-1)/2;
	for(int i=1;i<=n;++i) {
		if(a[i]>i&&a[a[i]]==i) ++ans;
	}
	printf("%lld\n",ans);
}

ABC262D I Hate Non-integer Number

分析

平均数是个整数,说明分子模分母为 \(0\)。既然是个计数问题,考虑 DP。

这类问题的一个技巧就是,将分子模分母的结果加入状态。这样要求的问题变为了 DP 求出的一个子问题,在面对某些有特殊条件限制问题时有奇效,这也是为什么很多时候都要“考虑问题的简化版”。一开始我根本无法理解这种做法,后来才慢慢明白,为了 DP 而去 DP,在很多时候时行不通的。

\(r\) 为选出 \(r\) 个数的阶段。当 \(r=1\) 时,贡献显然为 \(n\)。这东西加不加进状态都无所谓,反正就当个模数。

\(f(i,j,k)\) 为考虑前 \(i\) 个数,选出了 \(j\) 个数,这 \(j\) 个数之和模 \(r\)\(k\) 的方案数。注意如果是模 \(j\)\(k\) 的话,无后效性就没了。

由于存在取模,所以这类状态还是使用刷表法比较好。 \[ f(i-1,j-1,k) \rightarrow f(i,j,k+a_i \bmod r ) \] 答案为 \[ n + \sum_{r=2}^n f(n,r,0) \] 注意到对于每一个阶段 \(r\),相当于一个 0/1 背包问题,优化掉 \(i\) 这一维即可。

CODE

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=105, mod=998244353;
int n, ans, a[N], f[N][N];
int read() {
	int a=0, f=1; char c=getchar();
	while(!isdigit(c)) {
		if(c=='-') f=-1;
		c=getchar();
	}
	while(isdigit(c)) a=a*10+c-'0', c=getchar();
	return a*f;
}
signed main() {
	n=read();
	for(int i=1;i<=n;++i) a[i]=read();
	ans=n;
	for(int r=2;r<=n;++r) {
		memset(f,0,sizeof(f));
		f[0][0]=1;
		for(int i=1;i<=n;++i) {
			for(int j=min(i,r);j;--j) for(int k=0;k<r;++k)
				(f[j][(k+a[i])%r]+=f[j-1][k])%=mod;
		}
		(ans+=f[r][0])%=mod;
	}
	printf("%lld\n",ans);
}

ABC262E Red and Blue Graph

分析

提神醒脑数学题。

要求是

  1. 恰好有 \(k\) 个红点。
  2. 连接不同颜色点的边的数量为偶数。

设这 \(k\) 个红点的度数和为 \(Deg_k\),连接红点的边的数量为 \(Red\),连接不同颜色点的数量为 \(RedBlue\),那么由于每条连接红点的边在度数中被统计了两次,则 \[ Deg_k = 2 Red + RedBlue \] 注意到 \(Deg_k\)\(RedBlue\) 同奇偶。

题目要求 \(RedBlue\) 为偶数,可知 \(Deg_k\) 是偶数。于是乎求出每个节点的度数 \(deg_x\),数量为 \(odds\)

\(odds\) 中选出偶数 \(t\) 个点作为红点,在其他点中任选 \(k-t\) 个点作为蓝点,组合数求方案就行了。由于红点是计数的基准,所以蓝点的选择对答案没有贡献。

CODE

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5, mod=998244353;
int n, m, k, odd, ans, deg[N], fac[N], inv[N];
int read() {
	int a=0, f=1; char c=getchar();
	while(!isdigit(c)) {
		if(c=='-') f=-1;
		c=getchar();
	}
	while(isdigit(c)) a=a*10+c-'0', c=getchar();
	return a*f;
}
int fp(int x,int y) {
	int z=1; x%=mod;
	for(;y;x=x*x%mod,y>>=1) if(y&1) z=z*x%mod;
	return z;
}
int C(int n,int m) { return fac[n]*inv[m]%mod*inv[n-m]%mod; }
signed main() {
	n=read(), m=read(), k=read();
	for(int i=1;i<=m;++i) {
		int x=read(), y=read();
		++deg[x], ++deg[y];
	}
	fac[0]=fac[1]=1;
	for(int i=2;i<=n;++i) fac[i]=fac[i-1]*i%mod;
	inv[n]=fp(fac[n],mod-2);
	for(int i=n-1;~i;--i) inv[i]=inv[i+1]*(i+1)%mod;
	for(int i=1;i<=n;++i) if(deg[i]&1) ++odd;
	for(int i=0;i<=k;i+=2) {
		if(i<=odd&&k-i<=n-odd)
			(ans+=C(odd,i)*C(n-odd,k-i)%mod)%=mod;
	}
	printf("%lld\n",ans);
}

NowCoderT62C 莫娜与阿贝多

分析

注:你的最优策略是依赖于每一步的掉落结果的。

……

很好,和博弈论没有关系。

随机应变啊。

等等!这不就是 DP 中的最优子结构性质?

\(f(i,j,k)\)\(i\) 级物品剩下 \(j\) 个,\(i+1\) 级物品剩下 \(k\) 个,能达到目标的概率。

边界是 \(f(m,i,0) = 1\),其中 \(i \ge n\)

\(j \in [0,2]\) 时,无法进行合成 \[ f(i,j,k) = f(i+1,k,a_{i+2}) \] 否则 \[ f(i,j,k) = \max \begin{cases} p \cdot f(i,j-2,k+1) + (1-p) \cdot f(i,j-3,k+1) \\ q \cdot f(i,j-3,k+2) + (1-p) \cdot f(i,j-3,k+1) \end{cases} \] 答案 \(f(1,a_1,a_2)\)

CODE

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1005;
int n, m, a[N];
double p, q, f[10][N][N];
int read() {
	int a=0, f=1; char c=getchar();
	while(!isdigit(c)) {
		if(c=='-') f=-1;
		c=getchar();
	}
	while(isdigit(c)) a=a*10+c-'0', c=getchar();
	return a*f;
}
signed main() {
	n=read(), m=read();
	scanf("%lf%lf",&p,&q);
	for(int i=1;i<=m;++i) a[i]=read();
	for(int i=n;i<=1e3;++i) f[m][i][0]=1.0;
	for(int i=m-1;i;--i) {
		for(int j=0;j<=2;++j) for(int k=0;k<=1e3;++k) f[i][j][k]=f[i+1][k][a[i+2]];
		for(int j=3;j<=1e3;++j) for(int k=0;k<=1e3;++k) {
			double k1=p*f[i][j-2][k+1]+(1-p)*f[i][j-3][k+1];
			double k2=q*f[i][j-3][k+2]+(1-q)*f[i][j-3][k+1];
			f[i][j][k]=max(k1,k2);
		}
	}
	printf("%.6lf\n",f[1][a[1]][a[2]]);
}

「杂题选讲」#2
https://yozora0908.github.io/2022/tititi-solution-2/
作者
yozora0908
发布于
2022年8月4日
许可协议