2025 寒假集训 第二期

2025 寒假集训 第二期

J - Shift and Flip

题意:给出两个 \(01\)\(A,B\) ,要求使两串相等,可以执行以下三种操作

  1. \(A\) 左移一个单位
  2. \(A\) 右移一个单位
  3. 选择一个位置 \(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代表任意数)

\[1324-1243-1432 \\ =(1x2x-1423)-(14xx-1423)-(12xx-1234) \\ =1x2x-14xx-12xx+1234 \\=1x2x-1xxx+13xx+1234 \]

然后去预处理位置 \(i\) 左右两侧比 \(y_i\) 小的值,记为 \(L_i,R_i\) .

现在考虑上面式子的四项如何求:

1x2x : 枚举 2 的位置 \(i\) ,右侧排名为34,大于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;
}
posted @ 2025-02-11 15:45  Hastieyua  阅读(17)  评论(0)    收藏  举报