Codeforces Round 979 (Div. 2)
这场因为记错了时间导致我回到宿舍的时候已经十点半,比赛已经开始了半个小时。所以没打,直到今天才vp。
A. A Gift From Orangutan
题意
给你一个长度为 \(n\) 的数组 \(a\) ,定义数组 \(b_i=min(a_1,a_2...a_i)\) ,数组 \(c_i=max(a_1,a_2...a_i)\) 。你可以改变 \(a\) 的顺序使 \(\sum_{i=1}^{n} (c_i-b_i)\) 最大。
思路
当 \(i=1\) 的时候贡献一定为 \(0\) ,然后让 \(a\) 的最大值和最小值分别放在 \(i=1,i=2\) 的位置即可。
代码
void solve()
{
int n;
cin>>n;
vector<int> a(n+1);
for(int i=1;i<=n;i++) cin>>a[i];
sort(a.begin()+1,a.end());
int minn=a[1],maxn=a[n];
int ans=(n-1)*(maxn-minn);
cout<<ans<<endl;
}
B. Minimise Oneness
题意
\(t\) 是一个 \(0/1\) 串,定义 \(f(t)\) 是 \(t\) 中仅包含 \(0\) 的非空子序列的个数, \(g(t)\) 是 \(t\) 中至少包含一个 \(1\) 的非空子序列的个数。现在给你一个 \(n\) 表示 \(t\) 的长度,问你 \(|f(t)-g(t)|\) 的最小值是多少。
思路
一开始这个样例比较诈骗,很容易让人联想到输出一个 \(0/1\) 交替串,但是仔细想想发现这样肯定是错的。因为这样构造 \(0/1\) 的个数最多相差 \(1\),而 \(g(t)\) 的可供选择的子串肯定更多,因为 \(f(t)\) 只能包含 \(0\) ,而 \(g(t)\) 既能包含 \(0\) 又能包含 \(1\) 。
然后发现,对于一个 \(0\) 它可以对于剩下的所有 \(0\) 任意选择子序列,如果再考虑其他的 \(0\) 就会算重,所以 \(f(t)\) 的值就是 \(x-1\) 个 \(0\) 的所有子序列 \(+1\) , \(x\) 是 \(0\) 的个数。我们尽量的想让 \(g(t)\) 也尽量接近这个答案,那么只有一个 \(1\) 让这个 \(1\) 和剩下的所有 \(0\) 构成子序列一定是最接近的,这样的答案相当于 \(x\) 个 \(0\) 所有子序列的数量 \(+1\) 。易证明没有其他方案比这种更优。
代码
void solve()
{
int n;
cin>>n;
for(int i=1;i<=n-1;i++) cout<<0;
cout<<1<<endl;
}
C. A TRUE Battle
题意
给你一个 \(0/1\) 串, \(\text{Alice}\) 和 \(\text{Bob}\) 可以轮流在两个数中间添加一个 \(and\) 操作或者一个 \(or\) 操作,用操作的结果替换原先这两个位置上的数。 \(\text{Alice}\) 想让最后的序列为 \(1\) , \(\text{Bob}\) 想让最后的序列为 \(0\) , \(\text{Alice}\) 先手 ,问你最后 \(\text{Alice}\) 能不能赢。
思路
一开始没有好好读题导致 \(\text{WA}\) 了一发,他这个 \(and\) 和 \(or\) 是有先后顺序的,就和 \(\text{C++}\) 的优先级一样,先操作 \(and\) 再操作 \(or\) 。
于是我们考虑,如果最后 \(\text{Alice}\) 能赢,那么他一定要保护其中一个 \(1\) 到最后。如何保护 \(1\) 呢,我们不妨考虑 \(Bob\) 如何破坏掉所有的 \(1\) 。那么一个 \(1\) 肯定要和 \(0\) 相邻然后使用 \(and\) 操作就能破坏,如果 \(\text{Alice}\) 想保护 \(1\) 就得让这个 \(1\) 两边都是 \(or\) 。假设一个 \(1\) 两边都是 \(0\) ,那么 \(\text{Alice}\) 对其中一个位置 \(or\) 了, \(\text{Bob}\) 还是能在另一个位置放上 \(and\) 使得这个 \(1\) 被破坏掉。
综上如果 \(\text{Alice}\) 能保护一个 \(1\) ,那么就要保证这个 \(1\) 两边不能都是 \(0\) 就能获胜。
代码
void solve()
{
int n;string s;
cin>>n>>s;
int flag=0;
for(int i=0;i<n;i++)
{
if(i==0 && s[i]=='1') flag=1;
else if(i==n-1 && s[i]=='1') flag=1;
else if(s[i]=='1' && s[i-1]=='1' || s[i]=='1' && s[i+1]=='1') flag=1;
}
if(flag) cout<<"YES\n";
else cout<<"NO\n";
}
D. QED's Favorite Permutation
题意
给你一个长度为 \(n\) 的排列 \(p\) ,再给你一个长度为 \(n\) 操作序列 \(s\) ,\(s_i\in[L,R]\) 。你想让这个排列变成递增的,你可以进行任意次以下操作:
- 若 \(s_i=L\) ,交换 \(p_i\) 和 \(p_{i-1}\) 。
- 若 \(s_i=R\) ,交换 \(p_i\) 和 \(p_{i+1}\) 。
然后再给你 \(q\) 次查询,每次选择一个操作序列的位置更改,使这个位置变成相反的操作,即 \(L\) 变成 \(R\) , \(R\) 变成 \(L\) ,更改是持久的。问你对于每次更改的操作序列能不能经过一些操作让这个排列变成递增的。
思路
经过观察我们注意到,连续的 \(L\) 和连续的 \(R\) 中数的顺序是可以任意改变的。再仔细观察对于 \(R...RL...L\) 这样连续的 \(R\) 后面接上连续的 \(L\) 的序列中的数顺序也是可以任意改变的,至于为什么手动模拟一下就知道了。但是对于 \(L...LR...R\) 这种 \(L\) 在前面的操作序列后面 \(R\) 的数是交换不到前面 \(L\) 的,所以这里就相当于存在一些隔板,把这些数分成了好几段,数之间只能在同一段里面交换。
然后为了让 \(p\) 递增我们可以知道第 \(i\) 个数肯定要交换到 \(p_i\) 这个位置上的,问题就转化成了只需要判断 \(i\) 是不是和 \(p_i\) 在同一段就行。然后我的做法是对于每一个隔板看看有没有一些交换跨越了这个隔板,如果所有的隔板都没有被跨越,那么这个操作序列就是合法的。每个交换看成 \([l,r]\) ,隔板看成 \(pos\) ,现在就让你查询 小于 \(pos\) 的 \(l\) 所有区间中的 \(r\) 有没有大于 \(pos\) 的。对于这种经典左右区间问题可以用线段树,保证 \(l\) 有序的情况下把 \(r\) 放到线段树里面,但是他是静态的不需要修改,那么就可以考虑用个数组记录一下从这个位置往前的最大值是多少就行。
对于 \(q\) 次持久的更改,这种也非常经典,考虑每次更改影响了哪些位置,一般这种影响的位置都很少,所以可以单独操作被影响的位置。影响的只有 \(pos-1,pos,pos+1\) 这三个位置,然后判断修改前 \(pos-1,pos\) 和 \(pos,pos+1\) 是不是隔板,如果是就删掉。然后看修改后是不是隔板,如果是就加上。这里删除和添加我用的 \(\text{set}\) 维护。时间复杂度 \(O(nlogn)\) 。
代码
void solve()
{
int n,m;
cin>>n>>m;
vector<int> a(n+1),maxn(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(a[i]!=i)
{
int l=min(a[i],i),r=max(a[i],i);
maxn[l]=r;
}
}
for(int i=1;i<=n;i++)
maxn[i]=max(maxn[i-1],maxn[i]);
string s;
cin>>s;
s=' '+s;
set<pair<int,int>> pos;
int ok=0;
auto check=[&](int l,int r)
{
if(maxn[l]>=r) return 0;
return 1;
};
for(int i=1;i<=n;i++)
{
if(s[i]=='R' && s[i-1]=='L')
{
pos.insert({i-1,i});
if(check(i-1,i)) ok++;
}
}
while(m--)
{
int p;cin>>p;
if(s[p]=='R' && s[p-1]=='L')
{
if(check(p-1,p)) ok--;
pos.erase(pos.find({p-1,p}));
}
if(p!=n && s[p]=='L' && s[p+1]=='R')
{
if(check(p,p+1)) ok--;
pos.erase(pos.find({p,p+1}));
}
if(s[p]=='L') s[p]='R';
else s[p]='L';
if(s[p]=='R' && s[p-1]=='L')
{
if(check(p-1,p)) ok++;
pos.insert({p-1,p});
}
if(p!=n && s[p]=='L' && s[p+1]=='R')
{
if(check(p,p+1)) ok++;
pos.insert({p,p+1});
}
if(ok==pos.size()) cout<<"YES\n";
else cout<<"NO\n";
}
}