(CF2166) Codeforces Round 1064 (Div. 2) 题解
复建 CF 的第二场,打得更糖了。
A. Same Difference *800
给定长度为 \(n\) 的字符串 \(s\),每次操作可以将 \(s_i\) 替换为 \(s_{i+1}\),求最终使得 \(s\) 内每个字符都相同的最小操作次数。
\(2\le n\le 100\)。
1s 256MB
由于 \(s_n\) 无法修改,所以最终所有字符必然是 \(s_n\)。从每个 \(=s_n\) 的字符往前修改即可。答案即为 \(\sum\limits_{i=1}^n[s_i\neq s_n]\)。
赛时实现很糖,不建议参考 /xk。
#include<bits/stdc++.h>
#define N
#define ll long long
#define mod
using namespace std;
void sol()
{
int a[26]={0},n;
cin>>n;
string s;
cin>>s;
int maxs=0;
for(auto i:s) a[i-97]++;
cout<<n-a[s[n-1]-97]<<'\n';
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--) sol();
return 0;
}
B. Tab Closing *1000
一个浏览器窗口的宽为 \(a\),同时有 \(n\) 个网页标签在顶部。当窗口中有 \(m\) 个标签时,每个标签的宽度均为 \(\text{len}=\min\left(b,\dfrac am\right)\),并且每个标签的关闭按钮分别在距离窗口左侧 \(\text{len},2\cdot\text{len},\cdots,m\cdot\text{len}\) 的位置。
现在需要点击关闭按钮来关闭所有标签,初始鼠标指针在窗口最左侧,求整个移动过程中鼠标指针最少可以移动的次数。注意操作过程中标签宽度会变化。
\(1\le b\le a\le 10^9\),\(1\le n\le 10^9\)。
1.5s 256MB
显然当 \(m\le\left\lfloor\dfrac ab\right\rfloor\) 时,标签宽度固定为 \(b\),只需要将鼠标指针在 \(b\) 的位置一直关闭即可;否则标签会占满整个宽度为 \(a\) 的位置,只需要在 \(a\) 的位置一直关闭即可。注意特判 \(a=b\),答案即为 \(\left[\left\lfloor\dfrac ab\right\rfloor<n\land a\neq b\right]+1\)。
#include<bits/stdc++.h>
#define N
#define ll long long
#define mod
using namespace std;
void sol()
{
int a,b,n;
cin>>a>>b>>n;
cout<<(a!=b&&a/b<n?2:1)<<'\n';
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--) sol();
return 0;
}
C. Cyclic Merging *1200
给定一个长度为 \(n\) 的环,环上依次有非负整数 \(a_1,a_2,\cdots,a_n\)。每次操作可以选择一对相邻的数 \(x,y\),并将这两个数替换为 \(\max(x,y)\),并消耗 \(\max(x,y)\) 的代价。求使得序列中最终留下一个数的最小代价。
\(2\le n\le 2\times10^5\),\(0\le a_i\le 10^9\)。
2s 256MB
贪心,考虑从小到大删数,对于一个数看跟它左侧或右侧合并哪个更优,链表维护即可。时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
#define N 200005
#define ll long long
#define mod
using namespace std;
int n,a[N],pre[N],nxt[N];
void sol()
{
cin>>n;
int m=0;
ll ans=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(!m||a[i]!=a[m]) a[++m]=a[i];
else ans+=a[i];
}
if(a[1]==a[m]&&m>1) ans+=a[m--];
iota(pre+1,pre+m+1,0);
pre[1]=m;
iota(nxt+1,nxt+m+1,2);
nxt[m]=1;
set<pair<int,int>>st;
for(int i=1;i<=m;i++) st.insert({a[i],i});
for(auto [x,i]:st)
{
if(pre[i]==i) break;
ans+=min(a[pre[i]],a[nxt[i]]);
// cerr<<x<<','<<i<<' '<<ans<<'\n';
pre[nxt[i]]=pre[i];
nxt[pre[i]]=nxt[i];
}
cout<<ans<<'\n';
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--) sol();
return 0;
}
D. Marble Council *2000
给定一个长度为 \(n\) 的序列 \(a\),将其划分成任意多个子序列。对于每个子序列,取出它的众数(若存在多个,取任意一个)加入可重集合 \(S\)(初始 \(S=\varnothing\))。求可以得到的本质不同的 \(S\) 的个数。
\(1\le a_i\le n\le 5000\)。
2s 512MB
首先每个 \(S\) 内的元素都代表了一个子序列。
设 \(c_i\) 为数 \(i\) 在 \(a\) 中的出现次数。先考虑这样一类 \(S\):若有 \(x\in S\),则 \(x\) 在 \(S\) 中的出现次数恰为 \(c_x\)。考察这样的 \(S\) 合法情况及其影响。
-
若 \(|S|\ge\max\{c_i\}\),那么可以对于每个 \(i\notin S\),将 \(c_i\) 个 \(i\) 分散在 \(c_i\) 个子序列内,这样 \(S\) 内的每个元素始终是众数之一。
假设有一个 \(T\subset S\) 并且 \(T\) 中数的种类和 \(S\) 相同,此时必然存在一个正整数 \(i\),它在 \(S\) 中的出现次数多于 \(T\)。那么从 \(S\) 到 \(T\) 的过程相当于合并了某两个 \(i\) 代表的子序列。由于合并后的子序列 \(i\) 依旧是出现次数最多的数之一,因此 \(T\) 依旧合法。
-
若 \(|S|<\max\{c_i\}\),那么存在至少一个 \(c_i\) 没办法将 \(c_i\) 个 \(i\) 分散在恰好 \(c_i\) 个子序列内,此时必然存在一个子序列内 \(i\) 出现了多于一次,于是这时的 \(S\) 自然不合法。
同样地考虑 \(T\),将两个子序列合并后,要么是这个新的子序列不合法,要么是原来的某个子序列仍旧不合法。
所以我们只要考虑所有满足上述特殊条件的 \(S\) 是否合法即可。对于一个合法的 \(S\),它对答案贡献了 \(\prod\limits_{i\in S}c_i\)。
于是问题就变成了:求 \(c\) 所有合法的子序列的乘积和,一个子序列合法当且仅当其内元素之和不小于 \(\max\{c_i\}\)。
直接背包。设 \(f_{i,j}\) 表示考虑了正整数 \(1\sim i\),选出的数的元素之和为 \(j\) 的权值之和。容易得到:
答案即为 \(\sum\limits_{i=\max\{c_i\}}^nf_{n,i}\)。时间复杂度 \(O(n^2)\)。
#include<bits/stdc++.h>
#define N 5005
#define ll long long
#define mod 998244353
using namespace std;
ll qpow(ll x,ll y)
{
ll res=1;
x%=mod;
while(y)
{
if(y&1) res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
int n,a[N],cnt[N];
ll f[N][N];
void sol()
{
cin>>n;
fill(cnt+1,cnt+n+1,0);
int maxc=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
maxc=max(maxc,++cnt[a[i]]);
}
f[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=n;j++)
{
if(j<cnt[i])
{
f[i][j]=f[i-1][j];
continue;
}
f[i][j]=(f[i-1][j]+f[i-1][j-cnt[i]]*cnt[i]%mod)%mod;
}
}
ll ans=0;
for(int i=maxc;i<=n;i++) (ans+=f[n][i])%=mod;
cout<<ans<<'\n';
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--) sol();
return 0;
}
E. Binary Wine *2000
给定一个长度为 \(n\) 的非负整数序列 \(a\)。\(q\) 次独立的询问,每次询问给定一个 \(x\)。
对于每个询问,每次可以花费 \(1\) 的代价对某一个 \(a_i\) 执行 \(a_i\gets a_i+1\)。求能够使得 \(a\) 满足下列条件的最小代价:
- 存在一个正整数序列 \(b\),满足对于 \(\forall i\in[1,n]\),\(0\le b_i\le a_i\),且 \(\bigoplus\limits_{i=1}^nb_i=x\)。
\(1\le n\le 5\times10^5\),\(1\le q\le 5\times10^4\),\(0\le a_i,x<2^{30}\)。
2s 512MB
一个 \(b_i\) 会对 \(x\) 的二进制位上至少贡献一个数位,不然调整一下得到可以将这个数删去的后选择方案。贪心地,\(a_i\) 越大的可选择的数越多,因此只有前 \(30\) 大的 \(a_i\) 是有用的。
将这 \(30\) 个 \(a_i\) 放入一个大根堆中。从高到低考虑 \(x\) 的每个数位。设当前考虑到 \(2^i\) 位,堆顶的数为 \(d\)。讨论二者之间的关系:
- 若 \(d\ge 2^i\),那么 \(2^i\) 这个数位肯定可以被贡献,然后需要往低位考虑,弹出堆顶并向堆中加入 \(d-2^i\)。
- 若 \(d<2^i\),那么此时 \(2^i\) 这个数位没法被贡献,将 \(d\) 修改到 \(2^i\) 即可,对答案贡献 \(2^i-d\)。
时间复杂度 \(O(n\log n+q\log V\log\log V)\)。
#include<bits/stdc++.h>
#define N 500005
#define M 31
#define ll long long
#define inf (ll)1e18
#define mod
using namespace std;
int n,q,a[N],pre[M],nxt[M];
ll f[M][M<<1];
void sol()
{
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1,greater<int>());
while(q--)
{
int x;
cin>>x;
priority_queue<int>q;
for(int i=1;i<=min(30,n);i++) q.push(a[i]);
while(q.size()<30) q.push(0);
ll ans=0;
for(int i=30;~i;i--)
{
if(x>>i&1^1) continue;
int d=q.top();
q.pop();
if(d>=(1<<i)) q.push(d-(1<<i));
else ans+=(1<<i)-d;
}
cout<<ans<<'\n';
}
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--) sol();
return 0;
}
F. Path Split *2100
给定一个长度为 \(n\) 的正整数序列 \(a\),将 \(a\) 划分成若干个子序列 \(b_1,b_2,\cdots,b_k\),满足对于每个 \(b_i\),满足对于 \(\forall i\in[1,|b_i|)\),\(|b_{i,j}-b_{i,j+1}|=1\)。求 \(\min\{k\}\)。
\(1\le n\le 10^6\),\(1\le a_i\le 2n\)。
4s 512MB
分别对于奇数和偶数,按值和下标从小到大贪心地找到第一个可以匹配的位置进行匹配。时间复杂度 \(O(n\log n)\)。
正确性其实不是很难想,难点在于敢不敢写。这个过程本质上是将 \(a_i\) 按奇偶性划分后进行二分图匹配。\(k\) 越小,匹配越多,因此每个数都需要向后尽可能地匹配,匹配肯定不劣于不匹配。而从小到大贪,本质上是先把限制紧的数匹配掉。
#include<bits/stdc++.h>
#define N 1000005
#define ll long long
#define mod
using namespace std;
set<int>st[N<<1];
int n,a[N];
void sol()
{
cin>>n;
for(int i=1;i<=n*2+1;i++) st[i].clear();
for(int i=1;i<=n;i++)
{
cin>>a[i];
st[a[i]].insert(i);
}
int ans=n;
for(int i=1;i<=n*2;i+=2)
{
for(auto j:st[i])
{
if(!st[i-1].empty()&&*st[i-1].begin()<j)
{
auto pos=*--st[i-1].lower_bound(j);
ans--;
// cerr<<j<<'-'<<pos<<'\n';
st[i-1].erase(pos);
continue;
}
if(!st[i+1].empty()&&*st[i+1].begin()<j)
{
auto pos=*--st[i+1].lower_bound(j);
ans--;
// cerr<<j<<'-'<<pos<<'\n';
st[i+1].erase(pos);
continue;
}
}
}
for(int i=1;i<=n*2+1;i++) st[i].clear();
for(int i=1;i<=n;i++) st[a[i]].insert(i);
for(int i=2;i<=n*2;i+=2)
{
for(auto j:st[i])
{
if(!st[i-1].empty()&&*st[i-1].begin()<j)
{
auto pos=*--st[i-1].lower_bound(j);
ans--;
// cerr<<j<<'-'<<pos<<'\n';
st[i-1].erase(pos);
continue;
}
if(!st[i+1].empty()&&*st[i+1].begin()<j)
{
auto pos=*--st[i+1].lower_bound(j);
ans--;
// cerr<<j<<'-'<<pos<<'\n';
st[i+1].erase(pos);
continue;
}
}
}
cout<<ans<<'\n';
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--) sol();
return 0;
}

浙公网安备 33010602011771号