2025 寒假集训 第二期
2025 寒假集训 第二期
J - Shift and Flip
题意:给出两个 \(01\) 串 \(A,B\) ,要求使两串相等,可以执行以下三种操作
- 将 \(A\) 左移一个单位
- 将 \(A\) 右移一个单位
- 选择一个位置 \(i\) 满足 \(B_i=1\) ,使 \(A_i\) 取反
求最小操作数。
思路:不可能的情况只有当 \(B\) 全为 \(0\) 且 \(A\) 存在 \(1\) 的情况
其他情况显然都是有方法使得两串相等的。
操作可以视为移动操作和修改操作,由于 \(N<=2000\) ,所以考虑枚举 \(A,B\) 串的最终对应位置,这样就可以确定一部分的移动操作和修改操作的数量。
接下来的问题就是如何让需要修改的地方用最小的步数实现修改。
我们可以先预处理出 \(Lb_i,Rb_i\) 表示一个位置 \(i\) 向左/向右最少需要多少步可以移动到一个 \(B\) 为 \(1\) 的位置以实现修改
接着我们考虑整体该如何移动
整体会先向左移满足一些需要左移的失配点,再右移到目标位置,再右移去满足一些需要右移的失配点再归位(或者相反)
用线段表示的化就会是一个回形针的走向
我们想将失配点的 \(Lb_i 和 Rb_i\) 处理出来,每个失配点只需满足其一
按 \(Rb_i\) 从小到大排序后求出来 \(Lb_i\) 的后缀最大值,相加就可以求得失配移动的值
再加上目标移动和失配修改取最小值即可
实现:求 \(Lb_i,Rb_i\) 可以直接枚举处理
之后枚举 \(A\) 串开头对应的 \(B\) 串位置,求出失配点 \(Lb_i,Rb_i\)
排序后求后缀最大值
统计以失配点 \(i\) 为右侧移动最大值的情况下对目标向左/向右的求值,取最小值更新答案
res=min((p[j].r+max(i-1,suf[j+1]))*2-i+1+tot,res); //向左
res=min((suf[j+1]+max((n-i+1)%n,p[j].r))*2-(n-i+1)%n+tot,res);//向右
代码:
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define unmap unordered_map
#define unset unordered_set
#define MAXQ priority_queue
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define frep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
template<typename T> using MINQ=priority_queue<T,vector<T>,greater<T> >;
using pii=pair<int,int>;
using vi=vector<int>;
using vii=vector<vi>;
#define maxn 10010
struct kkk{
int l,r;
}p[maxn];
string s1,s2;
int n,ans=1e9;
int a[maxn],b[maxn],lb[maxn],rb[maxn],suf[maxn],sumb[maxn];
bool cmp(kkk a,kkk b){return (a.r==b.r)?(a.l<b.l):(a.r<b.r);}
void solve(){
cin>>s1>>s2;n=s1.size();
for(int i=0;i<n;i++)a[i+1]=s1[i]-'0';
for(int i=0;i<n;i++)b[i+1]=s2[i]-'0';
bool flag=0;
for(int i=1;i<=n;i++){
lb[i]=rb[i]=n;
for(int j=1;j<=n;j++){
if(b[j]){
lb[i]=min(lb[i],(i-j+n)%n);
rb[i]=min(rb[i],(j-i+n)%n);
flag=1;
}
}
}
if(!flag){
for(int i=1;i<=n;i++)
if(a[i]==1){
cout<<-1<<endl;
return;
}
cout<<0<<endl;
return;
}
for(int i=1;i<=n;i++){
int tot=0;
for(int j=1;j<=n;j++){
int k=i+j-1;if(k>n)k-=n;
if(a[k]!=b[j]){
p[++tot].l=lb[k];
p[tot].r=rb[k];
}
}
sort(p+1,p+tot+1,cmp);
suf[tot+1]=0;
int res=1e9;
for(int j=tot;j>=1;j--){
suf[j]=max(suf[j+1],p[j].l);
}
for(int j=0;j<=tot;j++){
res=min((p[j].r+max(i-1,suf[j+1]))*2-i+1+tot,res);
res=min((suf[j+1]+max((n-i+1)%n,p[j].r))*2-(n-i+1)%n+tot,res);
}
ans=min(ans,res);
}
cout<<ans<<endl;
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
int _t=1;
// cin>>_t;
//cout<<fixed<<setprecision(20);
for(int i=1;i<=_t;i++){
//cout<<"Case "<<i<<": ";
solve();
}
return 0;
}
H - 图腾
题意:[P4528 CTSC2008] 图腾 - 洛谷
思路:思维题,受教了
先约定abcd表示 \(1≤A<B<C<D≤n\) ,而且 \(y_a,y_b,y_c,y_d\) 的排名正好是 \(a,b,c,d\) 的方案数
那么题目要求的就是 1324-1243-1432
然后通过容斥去化简这个式子(x代表任意数)
然后去预处理位置 \(i\) 左右两侧比 \(y_i\) 小的值,记为 \(L_i,R_i\) .
现在考虑上面式子的四项如何求:
1x2x : 枚举 2 的位置 \(i\) ,右侧排名为3或4,大于2,所以有 \(n-i-R_i\) 种取法
左侧要满足1的位置 \(j\) ,和x的位置 \(k\) ,满足 \(j<k<i,y_j<y_i,y_k>y_i\)
再次使用容斥,用 \(y_j<y_i\) 的情况减去多算的 \(j<k,y_k<y_i\) 和 \(j≥k\) 的情况
\(j<k,y_k<y_i\) 的情况数为 \(C_{L_i}^2\) (左侧小值任取两个)
\(j≥k\) 的情况数为对于每个 \(j\) 都有 \(k\) 可取 \([1,j]\) 的值,用树状数组维护所有 \(y_j<y_i\) 的 \(j\) 值和即可
1xxx:即右侧任取三个大值,为 \(C_{n-i-R_i}^3\)
13xx:枚举3,右侧4有 \(n-i-R_i\) 种取法
采用容斥去处理 132 的相对关系,与上面思路差不多,用树状数组维护求和
1234:枚举3,右侧4为 \(n-i-R_i\) 种取法,前面如果2确定了放在 \(j\) 位置,1的放法就是\(L_j\) ,用树状数组维护求和
最后答案相加
代码:
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define unmap unordered_map
#define unset unordered_set
#define MAXQ priority_queue
using namespace std;
template<typename T> using MINQ=priority_queue<T,vector<T>,greater<T> >;
using pii=pair<int,int>;
using vi=vector<int>;
using vii=vector<vi>;
#define maxn 1000010
#define lowbit(x) (x&-x)
#define mod 16777216
int n,a[maxn],t[maxn],ans;
int L[maxn],R[maxn];
void add(int x,int val){while(x<=n)t[x]+=val,x+=lowbit(x);}
int query(int x){int ans=0;while(x)ans+=t[x],x-=lowbit(x);return ans;}
void solve(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
L[i]=query(a[i]);
R[i]=a[i]-L[i]-1;
add(a[i],1);
}
//1x2x+13xx+1234-1xxx
memset(t,0,sizeof(t));
for(int i=1;i<=n;i++){
ans+=(L[i]*(i-1)-query(a[i])-(L[i]*(L[i]-1)/2))*(n-i-R[i])%mod; //1x2x
ans%=mod;
add(a[i],i);
}
memset(t,0,sizeof(t));
for(int i=n;i>=1;i--){
ans+=(query(a[i])-(R[i]*(R[i]+1)/2))*(n-i-R[i])%mod; //13xx
ans%=mod;
add(a[i],a[i]);
}
memset(t,0,sizeof(t));
for(int i=1;i<=n;i++){
ans+=(n-i-R[i])*query(a[i])%mod; //1234
ans%=mod;
add(a[i],L[i]);
}
for(int i=1;i<=n;i++){ //1xxx
int x=n-i-R[i];
ans-=(x*(x-1)*(x-2)/6)%mod;
ans=(ans+mod)%mod;
}
cout<<ans<<endl;
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
int _t=1;
// cin>>_t;
//cout<<fixed<<setprecision(20);
for(int i=1;i<=_t;i++){
//cout<<"Case "<<i<<": ";
solve();
}
return 0;
}
D - 松鼠聚会
题意:平面上有 \(n\) 个点,选一个点使得该点到其他所有点的切比雪夫距离最短。
思路:这题主要是切比雪夫距离化为曼哈顿距离的转化
切比雪夫距离 $Disq(u,v)=max(|u_x-v_x|,|u_y-v_y|) $
曼哈顿距离 $Dism(u,v)=(|u_x-v_x|+|u_y-v_y|) $
转化: 来源OIWiki


知道了这个转化后我们将切比雪夫距离转化为曼哈顿距离
曼哈顿距离下 \(x\) 和 \(y\) 坐标的贡献可以分开计算
先讨论 \(x\) 坐标,对于一个点 \(i\) ,左侧贡献为 \((左侧点数*x_i-左侧点x坐标和)\) ,右侧贡献为 \((右侧点x坐标和-右侧侧点数*x_i)\)
同理 \(y\) 坐标
坐标和可以用前缀和计算,这样我们就可以快速统计每个点的贡献,取最小即为答案
代码:
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define unmap unordered_map
#define unset unordered_set
#define MAXQ priority_queue
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define frep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
template<typename T> using MINQ=priority_queue<T,vector<T>,greater<T> >;
using pii=pair<int,int>;
using vi=vector<int>;
using vii=vector<vi>;
#define maxn 1000010
int n,p[maxn],q[maxn],sumx[maxn],sumy[maxn],X[maxn],Y[maxn];
int ans=1e18;
void solve(){
cin>>n;
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
X[i]=x+y;Y[i]=x-y;
p[i]=X[i],q[i]=Y[i];
}
sort(p+1,p+n+1);
sort(q+1,q+n+1);
for(int i=1;i<=n;i++){
sumx[i]=sumx[i-1]+p[i];
sumy[i]=sumy[i-1]+q[i];
}
for(int i=1;i<=n;i++){
int cntx=lower_bound(p+1,p+n+1,X[i])-p;
int cnty=lower_bound(q+1,q+n+1,Y[i])-q;
int x=cntx*X[i]-sumx[cntx]+(sumx[n]-sumx[cntx])-(n-cntx)*X[i];
int y=cnty*Y[i]-sumy[cnty]+(sumy[n]-sumy[cnty])-(n-cnty)*Y[i];
ans=min(ans,x+y);
}
cout<<ans/2<<endl;
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
int _t=1;
// cin>>_t;
//cout<<fixed<<setprecision(20);
for(int i=1;i<=_t;i++){
//cout<<"Case "<<i<<": ";
solve();
}
return 0;
}
L - Clearing Up
题意:给定一个无向图,边有\(“SM”\)两色,构造一个生成树使得黑白边数相同,不能输出-1
思路:有意思的思维题
首先点数 \(n\) 一定为奇数,不然肯定不行
一步步去完成
1.将 \(S\) 边加入生成树,如果无法加入 \(n/2\) 条肯定不行,输出-1
2.将 \(M\) 边加入生成树,直到形成一棵树 ,不行输出-1
3.重新构造生成树,将 2 加入的边加入
4.将 \(M\) 边加入生成树,直到边数到达 \(n/2\) ,不行输出-1
此时可以保证生成树一定存在了,因为步骤4相当于保证图连通性下删去一条 \(S\) 边再加入一条 \(M\) 边。
5.将 \(S\) 边加入生成树,直到图连通(此时1步骤加入而5步骤不加入即为被删去的 \(S\) 边)
输出生成树即可
代码:
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define unmap unordered_map
#define unset unordered_set
#define MAXQ priority_queue
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define frep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
template<typename T> using MINQ=priority_queue<T,vector<T>,greater<T> >;
using pii=pair<int,int>;
using vi=vector<int>;
using vii=vector<vi>;
#define maxn 1000010
int n,m,cnt1,cnt2;
int fa[maxn],l[maxn],r[maxn],vis[maxn],ans[maxn];
char mode[maxn];
int find(int x){return fa[x]==x?fa[x]:find(fa[x]);}
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++)fa[i]=i;
if(n%2==0){
cout<<-1<<endl;
return;
}
for(int i=1;i<=m;i++){
cin>>l[i]>>r[i]>>mode[i];
}
for(int i=1;i<=m;i++){
if(mode[i]=='S'){
int u=find(l[i]),v=find(r[i]);
if(u!=v) fa[u]=v,cnt1++;
}
}
if(cnt1<n/2){
cout<<-1<<endl;
return;
}
for(int i=1;i<=m;i++){
if(mode[i]=='M'){
int u=find(l[i]),v=find(r[i]);
if(u!=v) fa[u]=v,cnt2++,vis[i]=1;
}
}
if(cnt1+cnt2<n-1){
cout<<-1<<endl;
return;
}
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++){
if(mode[i]=='M'){
if(vis[i]){
int u=find(l[i]),v=find(r[i]);
if(u!=v) fa[u]=v,ans[i]=1;
}
}
}
for(int i=1;i<=m;i++){
if(mode[i]=='M'){
if(!vis[i]){
int u=find(l[i]),v=find(r[i]);
if(u!=v) fa[u]=v,cnt2++,ans[i]=1;
if(cnt2>=n/2)break;
}
}
}
if(cnt2<n/2){
cout<<-1<<endl;
return;
}
for(int i=1;i<=m;i++){
if(mode[i]=='S'){
int u=find(l[i]),v=find(r[i]);
if(u!=v) fa[u]=v,ans[i]=1;
}
}
cout<<n-1<<endl;
for(int i=1;i<=m;i++)if(ans[i])cout<<i<<' ';
cout<<endl;
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
int _t=1;
// cin>>_t;
//cout<<fixed<<setprecision(20);
for(int i=1;i<=_t;i++){
//cout<<"Case "<<i<<": ";
solve();
}
return 0;
}
C - 发微博
题意:[P3998 SHOI2013] 发微博 - 洛谷
思路:我们考虑用户 \(x\) 对用户 \(y\) 所产生的贡献,为 \(x和y\) 好友期间 \(x\) 发的微博数。
那么在一个时间区间的统计值,我们可以考虑差分解决
也就是说,我们只要知道 \(x\) 和 \(y\) 开始当好友时 \(x\) 发的微博数和结束当好友时 \(x\) 发的微博数,就可以统计这个时间段 \(x\) 对 \(y\) 的贡献(多个时间段也一样)
那么最后再将全部人的好友关系都结束,就可以算出贡献了(就由我来讲这一切结束掉)
实现:可以使用set来维护每个人有的好友集合
发微博用个计数器可以维护
添加好友时删去微博数贡献,删除好友时加入微博数贡献,差分统计贡献
代码:
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define unmap unordered_map
#define unset unordered_set
#define MAXQ priority_queue
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define frep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
template<typename T> using MINQ=priority_queue<T,vector<T>,greater<T> >;
using pii=pair<int,int>;
using vi=vector<int>;
using vii=vector<vi>;
#define maxn 200010
int n,m;
int cnt[maxn],ans[maxn];
set<int>s[maxn];
void solve(){
cin>>n>>m;
for(int i=1;i<=m;i++){
char mode;int x,y;
cin>>mode>>x;
if(mode=='!'){
cnt[x]++;
}
if(mode=='+'){
cin>>y;
ans[x]-=cnt[y];ans[y]-=cnt[x];
s[x].insert(y);
s[y].insert(x);
}
if(mode=='-'){
cin>>y;
ans[x]+=cnt[y];ans[y]+=cnt[x];
s[x].erase(y);
s[y].erase(x);
}
}
for(int i=1;i<=n;i++){
for(auto it=s[i].begin();it!=s[i].end();it++){
ans[i]+=cnt[*it];
}
}
for(int i=1;i<=n;i++)cout<<ans[i]<<' ';cout<<endl;
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
int _t=1;
// cin>>_t;
//cout<<fixed<<setprecision(20);
for(int i=1;i<=_t;i++){
//cout<<"Case "<<i<<": ";
solve();
}
return 0;
}

浙公网安备 33010602011771号