Codeforces Round #743 (Div. 2)

本文解析了五道经典编程题目,包括模拟、思维+二分+双指针、bfs变种、构造和区间dp等算法的应用。通过具体实例详细阐述了解题思路和代码实现。

A.模拟

很明显,对于所有的非000的位,我们最好的处理方式为将其移动到个位,然后实行减一操作

#include<bits/stdc++.h>
using namespace std;
char s[110];
int main()
{
	int t;scanf("%d",&t);
	while (t--)
	{
		int n;scanf("%d",&n);
		scanf("%s",s+1);
		int ans = 0;
		for (int i=1;i<=n;++i)if (s[i]!='0')
		{
			++ans;
			ans+=(int)(s[i]-'0');
		}
		if (ans>0&&s[n]!='0')--ans;
		printf("%d\n",ans);
	}
}

B.思维+二分+双指针

考虑到a,ba,ba,b数组中的没有相同的数

那么aaa数组要是想要在字典序上小于bbb数组

aaa数组的第一位一定要小于bbb数组的第111

我们可以枚举所有的bbb数组的第一位的可能,计算aaa数组第一位小于bbb数组第一位的最小代价

即,如果我们选择b[i]b[i]b[i]bbb数组第一位,这个决定本身需要代价i−1i-1i1

然后我们找到最小的jjj使得a[j]<b[i]a[j]<b[i]a[j]<b[i]

因此总代价为i−1+j−1i-1+j-1i1+j1

jjj的操作,我们可以先得一个新数组c[i]=min(a[j],j≤i)c[i]=min(a[j],j\le i)c[i]=min(a[j],ji)

在,ccc中二分即可

当然我们还有双指针的线性解法

同样我们先得到ccc数组,然后枚举bbb数组

但是在枚举bbb数组时,我们可以注意到bbb数组的开销是一直增大的

倘若我们想得到更小的答案,那么aaa数组的jjj一定要减小的

换而言之,我们这次取的b[i]b[i]b[i]一定比上次小

jjj也只可能减小不可能增大

因此,双指针复杂度O(n)O(n)O(n)

//二分
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+100;
int a[maxn],b[maxn];
int n;
inline int solve(int x)
{
	int l = 1,r = n;
	int ans = 2*n;
	while (l<=r)
	{
		int mid = l+r>>1;
		if (a[mid]<x)
		{
			ans = mid;
			r = mid-1;
		}
		else
		{
			l = mid+1;
		}
	}return ans-1;
}
int main()
{
	int t;
	scanf("%d",&t);
	while (t--)
	{
		scanf("%d",&n);
		for (int i=1;i<=n;++i)scanf("%d",&a[i]);
		for (int i=1;i<=n;++i)scanf("%d",&b[i]);
		
		for (int i=2;i<=n;++i)a[i]=min(a[i],a[i-1]);
		int ans = 2*n;
		for (int i=1;i<=n;++i)
			ans = min(ans,solve(b[i])+i-1);
		printf("%d\n",ans);
	}
}
//双指针
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+100;
int a[maxn],b[maxn];
int n;
int main()
{
	int t;
	scanf("%d",&t);
	while (t--)
	{
		scanf("%d",&n);
		for (int i=1;i<=n;++i)scanf("%d",&a[i]);
		for (int i=2;i<=n;++i)a[i]=min(a[i-1],a[i]);
		for (int i=1;i<=n;++i)scanf("%d",&b[i]);
		int p1 = n,p2 = 1;
		
		int ans = 2*n;
		for (int i=1;i<=n;++i)if (b[i]>=b[p2])
		{
			p2 = i;
			while (p1>0&&a[p1]<b[p2])--p1;
			++p1;
			ans = min(ans,p1+p2-2);
		}
		printf("%d\n",ans);
		
	}
}

C.bfsbfsbfs

类似于bfsbfsbfs,是搜索的一种变种

每轮搜索我们从111遍历到nnn

去掉每一个度数为000的节点,同时更新出新的度数为000的节点

我们直到,当我们遍历到iii,她更新出了vvv,使得vvv的入度为000

  1. v>iv>iv>i本轮下vvv可以被遍历到
  2. v<iv<iv<i vvv只能留着下轮便利了

因此,我们不妨用setsetset维护本轮遍历的所有的度数为000的节点

对于v>iv>iv>i,我们将vvv放入本轮的setsetset

对于v<iv<iv<i我们新开一个setsetset作为下轮的放入

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+100;
vector<int> G[maxn];
int du[maxn];
int n;
int main()
{
	int t;scanf("%d",&t);
	while (t--)
	{
		set<int> se;
		int n;scanf("%d",&n);
		for (int i=0;i<=n;++i)G[i].clear();
		for (int i=1;i<=n;++i)
		{
			scanf("%d",&du[i]);
			for (int j=1;j<=du[i];++j)
			{
				int u;scanf("%d",&u);
				G[u].push_back(i);
			}
			if (du[i]==0)se.insert(i);
		}
		int ans=0;
		set<int> tmp;
		while (!se.empty())
		{
			int u = *se.begin();
			se.erase(se.begin());
			
			for (int v:G[u])
			{
				--du[v];
				if (du[v]==0)
				{
					if (v<u)tmp.insert(v);
					else se.insert(v);
				}
			}
			
			if (se.empty())
			{
				++ans;
				swap(se,tmp);
			}
			
		}
		for (int i=1;i<=n;++i)if (du[i]>0)
		{
			ans=-1;
			break;
		}
		printf("%d\n",ans);
	}
}

D.构造

a⊕b⊕ca\oplus b\oplus cabc a,b,c∈{0,1}a,b,c\in\{0,1\}a,b,c{0,1}

什么时候为111,什么时候为000

答案是,a+b+ca+b+ca+b+c为奇数的时候为111,偶数的时候为000

因此,我们可以确定。我们消去111的操作,一次操作只可以消去222

111个数的奇偶性不变,因此如果111的个数为奇数,那么直接不可能

我们选取iii,如果[ai,ai+1,ai+2][a_i,a_{i+1},a_{i+2}][ai,ai+1,ai+2][0,1,1],[1,0,1],[1,1,0][0,1,1],[1,0,1],[1,1,0][0,1,1],[1,0,1],[1,1,0]

我们可以消灭111

但是如果为[0,0,1],[1,0,0],[0,1,0][0,0,1],[1,0,0],[0,1,0][0,0,1],[1,0,0],[0,1,0]的话,就不可能了

例如[1,0,0,1,0][1,0,0,1,0][1,0,0,1,0]

我们可以先[1,1,1,1,0][1,1,1,1,0][1,1,1,1,0]然后[1,1,0,0,0][1,1,0,0,0][1,1,0,0,0],再[0,0,0,0,0][0,0,0,0,0][0,0,0,0,0]

主要是,我们需要去和另外一个奇数的111接轨!!

基于这种想法,创造出如下构造方案:

我们遍历i:1→n−2i:1\rightarrow n-2i:1n2

如果当前的a[i]==1:a[i]==1:a[i]==1:

​ 记a[i]+a[i+1]+a[i+2]=cnta[i]+a[i+1]+a[i+2]=cnta[i]+a[i+1]+a[i+2]=cnt

​ 1…cnt==2:cnt==2:cnt==2:我们直接进行操作,然后使得i=i+3i=i+3i=i+3

​ 2.cnt==1:cnt==1:cnt==1:我们进行操作,将a[i]=a[i+1]=a[i+2]=1a[i]=a[i+1]=a[i+2]=1a[i]=a[i+1]=a[i+2]=1,然后i=i+2i=i+2i=i+2

​ 3.cnt==3:cnt==3:cnt==3:直接i=i+2i=i+2i=i+2

进行这种操作一周后,剩下来的将是偶数长度的连续的111序列

我们只用222222个消掉就好了

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+100;
int a[maxn];
int n;
int main()
{
	int t;scanf("%d",&t);
	while (t--)
	{
		scanf("%d",&n);
		for (int i=1;i<=n;++i)scanf("%d",&a[i]);
		
		vector<int> res;
		for (int i=1;i+2<=n;)
		{
			if (a[i]==1)
			{
				int cnt = a[i]+a[i+1]+a[i+2];
				if (cnt&1)
				{
					if (cnt!=3)
					{
						res.push_back(i);
						a[i]=a[i+1]=a[i+2]=1;
					}
					i = i+2;
				}
				else
				{
					res.push_back(i);
					a[i]=a[i+1]=a[i+2]=0;
					i=i+3;
				}
			}
			else ++i;
		}
		
		for (int i=1;i+2<=n;++i)if (a[i]+a[i+1]+a[i+2]==2)
		{
			res.push_back(i);
			a[i]=a[i+1]=a[i+2]=0;
		}
		for (int i=n;i-2>=1;--i)if (a[i]+a[i-1]+a[i-2]==2)
		{
			res.push_back(i-2);
			a[i]=a[i-1]=a[i-2]=0;
		}
		
		bool f = true;
		for (int i=1;i<=n;++i)if (a[i]==1)
		{
			f=false;
			break;
		}
		
		if (!f)
		{
			printf("NO\n");
			continue;
		}
		printf("YES\n");
		printf("%d\n",(int)res.size());
		for (int num:res)printf("%d ",num);
		puts("");
	}
}

E.区间dpdpdp

看这个数据我们大概可以想到这是一个n2n^2n2级别的算法

首先我们说一个结论对一个区间[l,r][l,r][l,r],使得他们的颜色统一

目标颜色为a[l]a[l]a[l]时,所需的操作最少

文字不大好说明,举例试一试就会发现这个规律了

稍稍用到了一些容斥思想

设计dpdpdp状态:dp[i][j]:dp[i][j]:dp[i][j]

[i,j][i,j][i,j]变为**和a[i]**相同的颜色,可以减少的最大的数

(本来要将[i,j][i,j][i,j]变为相同的颜色的话,需要一个一个的染色,即j−ij-iji

那么最终的答案就是n−1−dp[1][n]n-1-dp[1][n]n1dp[1][n]

dpdpdp公式为dp[i][j]=max(dp[i+1][j],max(1+dp[i+1][k−1]+dp[k][j]∣a[i]==a[k]))dp[i][j] = max(dp[i+1][j],max(1+dp[i+1][k-1]+dp[k][j]|a[i]==a[k]))dp[i][j]=max(dp[i+1][j],max(1+dp[i+1][k1]+dp[k][j]a[i]==a[k]))

关于dp[i+1][j]dp[i+1][j]dp[i+1][j]的转移

我们可以认为将[i+1,j][i+1,j][i+1,j]转化为相同的颜色后,再次使用了一次操作将a[i]a[i]a[i][i+1,j][i+1,j][i+1,j]的颜色同化

关于max(1+dp[i+1][k−1]+dp[k][j]∣a[i]==a[k])max(1+dp[i+1][k-1]+dp[k][j]|a[i]==a[k])max(1+dp[i+1][k1]+dp[k][j]a[i]==a[k])

我们可以认为先将[i+1,k−1][i+1,k-1][i+1,k1]颜色归一,再将[k,j][k,j][k,j]归一

因为a[i]a[i]a[i]a[k]a[k]a[k]颜色相同,所以可以节省一步!

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5010;
int n, a[maxn], la[maxn], fn[maxn];
int f[N][N];
int main() {
    int t;scanf("%d", &t);
    while (t--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) fn[i] = 0;
        for (int i = 1; i <= n; ++i) {
            scanf("%d", a + i);
            if (a[i] == a[i - 1]) {
                --i; --n;
            } else {
                la[i] = fn[a[i]];
                fn[a[i]] = i;
            }
        }
        for (int i = n; i; --i) {
            for (int j = i + 1; j <= n; ++j) {
                f[i][j] = f[i][j - 1];
                for (int k = la[j]; k >= i; k = la[k]) {
                    f[i][j] = max(f[i][j], f[i][k] + f[k][j - 1] + 1);
                }
            }
        }
        printf("%d\n", n - 1 - f[1][n]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值