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 i−1
然后我们找到最小的 j j j使得 a [ j ] < b [ i ] a[j]<b[i] a[j]<b[i]
因此总代价为 i − 1 + j − 1 i-1+j-1 i−1+j−1
找 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],j≤i)
在, 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
- v > i v>i v>i本轮下 v v v可以被遍历到
- 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 a⊕b⊕c 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:1→n−2
如果当前的 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 j−i)
那么最终的答案就是 n − 1 − d p [ 1 ] [ n ] n-1-dp[1][n] n−1−dp[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][k−1]+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][k−1]+dp[k][j]∣a[i]==a[k])
我们可以认为先将 [ i + 1 , k − 1 ] [i+1,k-1] [i+1,k−1]颜色归一,再将 [ 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;
}