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 10010struct 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 16777216int 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-1xxxmemset(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; //1x2xans%=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; //13xxans%=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; //1234ans%=mod;add(a[i],L[i]);}for(int i=1;i<=n;i++){ //1xxxint 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 1000010int 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 1000010int 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 200010int 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;
}