Codeforces Round #743 (Div. 2)

A.模拟

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

#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 , b a,b a,b数组中的没有相同的数

那么 a a a数组要是想要在字典序上小于 b b b数组

a a a数组的第一位一定要小于 b b b数组的第 1 1 1

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

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

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

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

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

在, c c c中二分即可

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

同样我们先得到 c c c数组,然后枚举 b b b数组

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

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

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

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

因此,双指针复杂度 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. b f s bfs bfs

类似于 b f s bfs bfs,是搜索的一种变种

每轮搜索我们从 1 1 1遍历到 n n n

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

我们直到,当我们遍历到 i i i,她更新出了 v v v,使得 v v v的入度为 0 0 0

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

因此,我们不妨用 s e t set set维护本轮遍历的所有的度数为 0 0 0的节点

对于 v > i v>i v>i,我们将 v v v放入本轮的 s e t set set

对于 v < i v<i v<i我们新开一个 s e t set set作为下轮的放入

#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 ⊕ c a\oplus b\oplus c abc a , b , c ∈ { 0 , 1 } a,b,c\in\{0,1\} a,b,c{0,1}

什么时候为 1 1 1,什么时候为 0 0 0

答案是, a + b + c a+b+c a+b+c为奇数的时候为 1 1 1,偶数的时候为 0 0 0

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

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

我们选取 i i i,如果 [ a i , a i + 1 , a i + 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]

我们可以消灭 1 1 1

但是如果为 [ 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]

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

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

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

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

​ 记 a [ i ] + a [ i + 1 ] + a [ i + 2 ] = c n t a[i]+a[i+1]+a[i+2]=cnt a[i]+a[i+1]+a[i+2]=cnt

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

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

​ 3. c n t = = 3 : cnt==3: cnt==3:直接 i = i + 2 i=i+2 i=i+2

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

我们只用 2 2 2 2 2 2个消掉就好了

#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.区间 d p dp dp

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

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

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

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

稍稍用到了一些容斥思想

设计 d p dp dp状态 : d p [ i ] [ j ] :dp[i][j] :dp[i][j]

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

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

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

d p dp dp公式为 d p [ i ] [ j ] = m a x ( d p [ i + 1 ] [ j ] , m a x ( 1 + d p [ i + 1 ] [ k − 1 ] + d p [ 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]))

关于 d p [ 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]的颜色同化

关于 m a x ( 1 + d p [ i + 1 ] [ k − 1 ] + d p [ 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、付费专栏及课程。

余额充值