牛客 小白114 20250418

牛客 小白114 20250418

https://ac.nowcoder.com/acm/contest/106859

写在开头:

这场的题面超级史

A:

题目大意:

void solve(){
	int n;
	cin>>n;
	int x;
	for (int i=1;i<=n;i++){
		cin>>x;
		if (i==1) cout<<min(x+1,5)<<' ';
		else cout<<min(x-1,5)<<' ';
	}
}

签到

B:

题目大意:有 \(n\) 张牌,每张牌的点数为 \(a_i\) ,当出第 \(i\) 张牌且这张牌的点数 \(a_i=i\) 时可以累加 \(i\) 到总值上,求最大总值

unordered_map<int,int> mp;

void solve(){
	int n;
	cin>>n;
	for (int i=1;i<=n;i++){
		int x;
		cin>>x;
		mp[x]++;
	}
	LL ans=0;
	for (int i=1;i<=n;i++)
		if (mp[i]) ans+=1ll*i;
	cout<<ans;
}

可以知道每张点数为 \(a_i\) 的牌最多也只能对答案贡献一次 \(a_i\)

所以用一个桶记录每张牌的点数,然后依次遍历出的第 \(i\) 张牌,如果这张牌能在桶中找到 \(a_i=i\) ,那么就可以在此时出这张牌然后累加贡献

C:

题目大意:给出一张有向图,和一个点,判断是否存在这个点是否在环上

int n,x;
vector<int> e[100010];
bool vis[100010];

bool dfs(int u){
	for (auto v:e[u]){
		if (vis[u]) return 1;
		vis[v]=1;
		if (dfs(v)) return 1;
		vis[v]=0;
	}
	return 0;
}

void solve(){
	cin>>n>>x;
	for (int i=1;i<=n;i++){
		int v;
		cin>>v;
		e[v].push_back(i);
	}
	if (dfs(x)) cout<<"Yes";
	else cout<<"No";
}

相对有点板,从给出的这个点开始 DFS,如果在过程中遇到了之前 DFS路径中访问过的点,说明存在环,那么就返回

for (auto v:e[u]){
	if (vis[u]) return 1;//如果这个点已被访问过,存在环
	vis[v]=1;//标记这个点被走过
	if (dfs(v)) return 1;//dfs到v点
	vis[v]=0;//恢复路径
}

D:
题目大意:

LL n,k;
LL a[100010];
LL pre[100010],suf[100010];
LL sufmx[100010];

bool judge(LL x){
	return x*(x-1)/2<k+x;
}

void solve(){
	cin>>n>>k;
	for (int i=1;i<=n;i++) cin>>a[i];
	LL l=1,r=1e9;
	while (l+1!=r){
		LL mid=l+r>>1;
		if (judge(mid))	
			l=mid;
		else
			r=mid;
	}
	LL op=min(n,l);
	for (int i=1;i<=n;i++) pre[i]=pre[i-1]+a[i];
	for (int i=n;i>=1;i--){
		suf[i]=suf[i+1]+a[i];
		sufmx[i]=max(sufmx[i+1],suf[i]);//计算最大后缀和
	} 
	LL sum=0;
	for (int i=0;i<=op;i++)
		sum=max(pre[i]+sufmx[n-op+i+1],sum);
	cout<<sum;
}

题目有点史诗,赛时拿DP乱搞了,其实用前后缀维护一下就行

可以通过二分计算最大摸牌次数,注意摸上来的牌也可以弃掉

bool judge(LL x){
	return x*(x-1)/2<k+x;//等差数列计算弃牌数
}

操作数不能超过牌堆数,所以需要比较取最小

然后枚举前缀和与后缀最大值,最大的价值可以被表示为两端从前后延伸的区间,即一段前缀和与一段后缀和

我们可以选择发动技能的次数,所以在锚定一段前缀和后,我们在后缀和中找剩余摸牌数下的最大后缀和

假设在牌堆顶摸 \(i\) 张牌,那么剩下的 \(op-i\) 张牌需要从牌堆底摸取,当然这 \(op-i\) 张牌不是必须要取走的

只需要在 \([n-(op-i)+1,n]\) 这段后缀和中找到其中的最大后缀和即可

最大后缀和可以表示后缀在区间 \([k,n]\) 的区间和 ,其中 \(k\in [n-(op-i)+1,n]\)

E:

题目大意:

struct node{
	int cnt,mp;
};

node st[4];
int a[100010];
int n;

void solve(){
	cin>>st[1].cnt>>st[2].cnt>>st[3].cnt;
	for (int i=1;i<=3;i++){
		st[i].mp=i;
		n+=st[i].cnt;
	}
	for (int i=1;i<=n;i++) cin>>a[i];
	sort(st+1,st+4,[](node x,node y){return x.cnt<y.cnt;});
	if (st[3].cnt>=(st[1].cnt+st[2].cnt)){
		for (int i=1;i<=n;i++){
			if(a[i]==st[3].mp){
				if(st[1].cnt){
					cout<<st[1].mp<<' ';
					st[1].cnt--;
				}else if(st[2].cnt){
					cout<<st[2].mp<<' ';
					st[2].cnt--;
				}
				else
					cout<<st[3].mp<<' ';
			}
			else
				cout<<st[3].mp<<' ';
		}
	}else{
		int cnt31=st[1].cnt;
		int cnt13=st[3].cnt-st[2].cnt;
		for (int i=1;i<=n;i++){
			if (a[i]==st[3].mp){
				if(cnt31){
					cout<<st[1].mp<<' ';
					cnt31--;
				}else{
					cout<<st[2].mp<<' ';
				}
			}
			if (a[i]==st[2].mp){
				cout<<st[3].mp<<' ';
			}
			if (a[i]==st[1].mp){
				if(cnt13){
					cout<<st[3].mp<<' ';
					cnt13--;
				}else{
					cout<<st[2].mp<<' ';
				}
			}
		}
	}
}

又是一道重量级的模拟题,贪心做法

假设这三张牌的数量分别为 \(a\le b\le c\) ,对应的牌为 \(A,B,C\),友诸葛亮预测的牌为 \(x_i\)

  • \(c\ge (a+b)\) 时, \(x_i=A,B\) 时构造 \(C\) ,那么一定会剩余 \(c-(a+b)\)\(C\) 会被预测到

  • \(c< (a+b)\) 时,存在这样一种构造方案:

    \[\begin{array}{|l|l|l|} \hline A&B&C\\ \hline & & A:a\\ \hline B:(a-(c-b))&&B:(c-a)\\ \hline C:(c-b)&C:b&\\ \hline \end{array} \]

    能够使得被预测的牌为 \(0\)

模拟时,利用结构体记录每种类型的牌的数量

struct node{
	int cnt,mp;//数量,类型
};
sort(st+1,st+4,[](node x,node y){return x.cnt<y.cnt;});

其中 mp 用来保护牌按照数量进行排序后,仍然能映射牌的类型

按照牌的数量从小到大排序后的 st[1,2,3].mp 可以被类似为上面假设的 \(A,B,C\)

F:

题目大意:

int a[110][110],b[110][110],c[110];
int dp[110][110][110];

void solve(){
	int n,x,y;
	cin>>n>>x>>y;
	for (int i=1;i<=n;i++){
		cin>>c[i];
		for (int j=1;j<=c[i];j++)
			cin>>a[i][j];
		for (int j=1;j<=c[i];j++)
			cin>>b[i][j];
		a[i][0]=x;
		b[i][0]=y;
	}
	int ans=0;
	for (int i=1;i<=n;i++){
		for (int j=0;j<=n;j++){
			for (int k=0;k<=i;k++){
				for (int u=0;u<=c[i];u++){
					if (k>=1)
						dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][k-1]);
					if(j-b[i][u]>=0&&k<i)
						dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-b[i][u]][k]+a[i][u]);
					if (j<=k) ans=max(ans,dp[i][j][k]);
				}
			}
		}
	}	
	cout<<ans<<endl;
}

分组背包问题

定义 \(dp_{i,j,k}\) 表示前 \(i\) 组在背包容量为 \(j\) 下,且吃了 \(k\) 个桃的最大价值

因为桃和其他手牌都可以保留在背包中,但是桃可以选择吃或不吃,所以对于每组物品都要考虑对于这组物品是否选择吃他的桃

将桃这张牌存在每组物品中,价值和体积为 \(x,y\)

那么状态的转移可以被定义为:对于第 \(i\) 组物品,如果选择吃这组物品的桃,那么:

\[dp_{i,j,k}={\rm{max}}(dp_{i,j,k},dp_{i-1,j,k-1}) \]

由第 \(i-1\) 组物品背包容量为 \(j-1+1\) 吃了 \(k-1\) 个桃的状态转移而来,其中 \(j-1+1\) 表明吃桃后背包容量的改变

对于第 \(i\) 组物品,如果选择不吃这组物品的桃,那么对于这组背包中的每个物品都有:

\[dp_{i,j,k}={\rm{max}} (dp_{i,j,k},dp_{i-1,j-b_{i,u},k}) \]

此时如果选取桃牌,这张牌就当做一张其他牌处理,即不吃

由第 \(i-1\) 组物品背包容量为 \(j-b_{i,u}\) 吃了 \(k\) 个桃的状态转移而来,注意这里的 \(k\) 需要满足小于 \(i\) 的前提

这是因为,如果在考虑当前组背包不吃桃后,那么背包容量最大为 \(i-1\) 即前面全吃桃的背包体积

posted @ 2025-04-20 16:24  才瓯  阅读(7)  评论(0)    收藏  举报