codeforces 1291 题解(完结)
codeforces 1291 题解
A. Even But Not Even
要求这个数本身不能被 \(2\) 整除,加和可以被 \(2\) 整除,那我们分类讨论
- 若该数本身已经不能被 \(2\) 整除,但是加和可以,那我们什么都不用做即可;若加和不可以,我们从后往前删一个奇数即可。
- 若该数本身就不能被 \(2\) 整除,我们从后往前删奇数,这样就能转化成上一种情况。
删到最后删不动了或者删空了就是不行,输出 \(-1\)。
时间复杂度 \(O(n)\),代码写的有点乱。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define _for(i,a,b) for(int i = (a);i < b;i ++)
#define _rep(i,a,b) for(int i = (a);i > b;i --)
#define INF 0x3f3f3f3f3f3f3f3f
#define ZHUO 11100000007
#define MOD 1000000007
#define MIKUNUM 39
#define pb push_back
#define debug() printf("Miku Check OK!\n")
#define maxn 50003
#define X first
#define Y second
int main()
{
ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--)
{
string s;int n;
cin >> n >> s;
int a = 0, b = 0;
int index = -1;
a = s[s.size()-1]-'0';
_for(i,0,s.size())
b += s[i]-'0';
if(!(a&0x1))
{
while(!s.empty() && !((s[s.size()-1]-'0')&0x1))
s.pop_back();
}
if(b&0x1)
{
for(int i = s.size()-2; i>=0; i --)
{
if((s[i]-'0')&0x1)
{
index = i;
break;
}
else
continue;
}
}
else
index = 100000;
if(s.empty() || index==-1)
printf("-1\n");
else
{
_for(i,0,s.size())
if(i!=index)
printf("%c",s[i]);
printf("\n");
}
}
return 0;
}
B. Array Sharpening
他问你能不能构造出来,我们先假设能构造出来,那数组就分成两部分,我们将前一部分称为 上升部分,后一部分称为 下降部分。
我们希望什么呢?因为改变数字大小不计代价,所以上升部分越靠前的数越小越好,最小能是多少?答案就是下标为 \(i(1≤i≤n)\) 的地方最小为 \(i-1\),好给上升部分后面的数“腾位置”。
真就有一个元素达不到这个标准怎么办( \(a[i] < i-1\) )?那我们假设已经进入了 下降部分。首先有一点,下降部分都不能比 顶峰(上升部分的最后一个元素或者看作下降部分的第一个元素)大,或者一样大。其次我们想让他越大越好,这是给下降部分后面的数 "腾位置"。如果还有一个元素不能满足标准,他不够大,不能给后面的元素预留最小的足够空间(\(a[i]<n-i-1\)),那我们就知道,这数组变不成我们想要的数组了。
时间复杂度 \(O(n)\)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define _for(i,a,b) for(int i = (a);i < b;i ++)
#define _rep(i,a,b) for(int i = (a);i > b;i --)
#define INF 0x3f3f3f3f3f3f3f3f
#define ZHUO 11100000007
#define MOD 1000000007
#define MIKUNUM 39
#define pb push_back
#define debug() printf("Miku Check OK!\n")
#define maxn 350003
#define X first
#define Y second
int a[maxn];
int main()
{
ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--)
{
int n;
cin >> n;
_for(i,0,n)
cin >> a[i];
int can = 1;
int st = -1;
_for(i,0,n)
if(a[i]<i)
{
st = i;
break;
}
if(st==-1)
goto end;
_for(i,st,n)
{
a[i] = min(a[i],a[st-1]-1);
if(a[i]<n-i-1)
{
can = 0;
break;
}
}
end:
if(can)
cout << "Yes" << endl;
else
cout << "No" << endl;
}
return 0;
}
C. Mind Control
首先有一点可以确定:你控制你之后的人没有用,所以 \(k\) 最大为 \(m-1\)。然后我们也不用考虑具体怎么控制这些人。观察一下,不管怎么取,最后一定是剩下一个长度为 \(n-m+1\) 的原数组的子数组让你取,而这个子数组具体是哪个子数组,这要看两部分内容:
- 你控制了多少个人取头部,人数我们记为 \(x\)
- 你没法控制的人中,有多少个取了头部,人数我们记为 \(y\)
所以这个子数组就敲定下来了,因为你也只能取头部或者尾部,所以最后答案就是 \(max(a_{1+x+y},a_{1+x+y+(n-m)})\)
这样就有一个很显然的 \(O(n^2)\) 算法,双重循环遍历 \(x\) 和 \(y\) 即可,我们记 \(b_i = \max(a_{1+i}, a_{1+i+(n-m)})\)
最终答案就是 \(\displaystyle \max_{x \in [0 , k]} \bigg[ \min_{y \in [0 , m-1-k]} b_{x+y} \bigg]\)
解释一下,最后的子数组你一定是取子数组两端的最大值,所以就是 \(b_{x+y}\),但是因为你不确定你没法控制的人都取了什么,你只能按最坏情况考虑,他们都在拆你的台,因此就有在 \(x\) 敲定后, \(y \in[0,m-1-k]\) 中你所能取到的 \(b_{x+y}\) 中的最小值,而在这些最小值中,因为 \(x\) 是你可以控制的,所以取这些最小值中的最大值就是答案。
我们将这个最终答案式等价变形为 \(\displaystyle \max_{x \in [0 , k]} \bigg[ \min_{y' \in [x , x+m-1-k]} b_{y'} \bigg]\) ,也就是令 \(y'=x+y\) ,这样换式子,问题就可以被更好的解释成 :对于数组 \(b\),长度为 \(m-k\) 的滑动数组中的最小值的最大值,滑动数组头部的取值为 \([0,k]\)。
用线段树搞一搞就是 \(O(nlogn)\) 的解法,用单调队列搞一搞就是 \(O(n)\),代码是单调队列实现的。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define _for(i,a,b) for(int i = (a);i < b;i ++)
#define _rep(i,a,b) for(int i = (a);i > b;i --)
#define INF 0x3f3f3f3f
#define ZHUO 11100000007
#define MOD 1000000007
#define MIKUNUM 39
#define pb push_back
#define debug() printf("Miku Check OK!\n")
#define maxn 3503
#define X first
#define Y second
int t;
int n, m, k;
int a[maxn];
int getb(int i)
{
return max(a[1+i],a[1+i+(n-m)]);
}
int main()
{
ios::sync_with_stdio(false);
cin >> t;
while(t--)
{
cin >> n >> m >> k;
_for(i,1,n+1)
cin >> a[i];
k = min(k,m-1);
deque<int> dq;
int ans = -INF;
for(int y = 0; y <= m-1;y ++)
{
//维护滑动数组长度
while(dq.size() && dq.front() <= y-(m-k))
dq.pop_front();
//若当前不可能成为该滑动数组最小则出队
while(dq.size() && getb(y) <= getb(dq.back()))
dq.pop_back();
dq.pb(y);
//等滑动数组长度足够时再计算答案
if(dq.size() && y >= m-k-1)
ans = max(ans,getb(dq.front()));
}
printf("%d\n",ans);
}
return 0;
}
\(D\) 题字符串交给队友
E. Prefix Enlightenment
我们可以将子数组看成 点 ,主数组的下标看成线,若两个不同的子数组中存在至少一个相同的主数组下标,那我们就可以认为这两个子数组是有联系的。具体联系体现在:
- 当主数组某一位为 \(0\) 时,有联系的两个子数组必定只选取一个
- 当主数组某一位为 \(1\) 时,有联系的两个子数组必定同时选取或不选取
有没有一点 种类并查集 的感觉,那我们拆点,把点拆成两个状态:选取与不选取,并且给选取赋权值为 \(1\),不选取赋权值为 \(0\)。我们将两个子数组各自的选取与不选取状态与对方的选取与不选取状态连起来,说明他们有联系。具体的联系方式为:
- 当主数组某一位为 \(0\) 时,一个子数组的选取与另一个子数组的不选取连接
- 当主数组某一位为 \(1\) 时,一个子数组的选取与另一个子数组的选取,不选取与不选取连接
这样就算是把主数组分为两个不同的 命题 ,这两个命题都可以为真,就看你选哪个了,那自然是选权值和最小的一个。为什么一定不会矛盾?因为题目保证一定存在一种选取方案使得主数组每一元素都翻转为 \(1\) 。
这样感觉就大功告成了,不过我们再考虑一下,题目说最多两个,那一个和没有的情况呢?
因为题目保证一定存在一种选取方案使得主数组每一元素都翻转为 \(1\) ,所以如果某一个主数组下标在子数组中没有出现,那原数组一定为 \(1\) ,我们什么都不用做即可。
如果是出现一次,那就有:
- 当主数组某一位为 \(0\) 时,该子数组必定选取
- 当主数组某一位为 \(1\) 时,该子数组必定不选取
这怎么连接命题?我们可以设一个点,将他赋权值为 \(+∞\) ,若必定选取,则将该点不选取状态与他相连即可,必定不选取时同理。
问题又来了,你说好两个命题呢,那这种必定选取或者不选取我们怎么统计啊?
我们就可以使用动态更新 \(ans\) 的思想,当要更新一个或者两个点的时候,先消除他们在之前对答案的贡献,将该点更新过后,再将其加入。
时间复杂度 \(O((n+k)α(n))\) 。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define _for(i,a,b) for(ll i = (a);i < b;i ++)
#define _rep(i,a,b) for(ll i = (a);i > b;i --)
#define INF 0x3f3f3f3f
#define ZHUO 11100000007
#define MOD 1000000007
#define MIKUNUM 39
#define pb push_back
#define debug() prllf("Miku Check OK!\n")
#define maxn 300039*2
#define X first
#define Y second
ll n, k;
string lamp;
vector<ll> have[maxn];
struct Djs
{
ll par[maxn];
ll high[maxn];
ll d[maxn];
void renew()
{
_for(i,1,maxn)
{
par[i] = i;
high[i] = 0;
if(i<=k)
d[i] = 1;
else
d[i] = 0;
}
}
ll find(ll x)
{
return par[x] == x ? x : par[x] = find(par[x]);
}
void unite(ll x,ll y)
{
x = find(x);
y = find(y);
if(x==y) return ;
ll dx = d[x], dy = d[y];
d[x] += dy;
d[y] += dx;
if(high[x]<high[y])
par[x] = y;
else
{
par[y] = x;
if(high[x]==high[y])
high[x] ++;
}
}
bool same(ll x,ll y)
{
return find(x) == find(y);
}
} djs;
int main()
{
ios::sync_with_stdio(false);
cin >> n >> k;
string tmp;
cin >> tmp;
//补位
lamp += '6';
lamp += tmp;
_for(i,1,k+1)
{
ll tn, tmp;
cin >> tn;
_for(j,0,tn)
{
cin >> tmp;
//have[i]表示主数组下标 i 时有哪些子数组里包含 i
have[tmp].pb(i);
}
}
ll ans = 0;
djs.renew();
//st就是无穷节点
int st = 600039;
djs.d[st] = INF;
_for(i,1,n+1)
{
//主数组下标i只存在一个子数组包含i
if(have[i].size()==1)
{
int x = have[i][0];
//消除该子数组之前对答案的影响
ans -= min(djs.d[djs.find(x)],djs.d[djs.find(x+k)]);
if(lamp[i]=='1')
djs.unite(x,st);
else
djs.unite(x+k,st);
//只有两种可能:选这个节点与不选这个节点
ans += min(djs.d[djs.find(x)],djs.d[djs.find(x+k)]);
}
else if(have[i].size()==2)
{
int x = have[i][0], y = have[i][1];
//如果他们已经联系过了,则没有更新发生
if(!djs.same(x,y) && !djs.same(x,y+k))
{
//消除子数组 x 和 y的影响,因为他们马上要合起来考虑
ans -= min(djs.d[djs.find(x)],djs.d[djs.find(x+k)])
+ min(djs.d[djs.find(y)],djs.d[djs.find(y+k)]);
if(lamp[i]=='1')
djs.unite(x,y),djs.unite(x+k,y+k);
else
djs.unite(x+k,y),djs.unite(x,y+k);
ans += min(djs.d[djs.find(x)],djs.d[djs.find(x+k)]);
}
}
printf("%d\n",ans);
}
return 0;
}
\(F\) 题没时间了,不看了