SEERC2022(E,F,H,K)

正题

比赛链接:https://codeforces.com/gym/104114


E-Exercise【dp,贪心】

题目大意

给出长度为 2 n 2n 2n 的序列 c i c_{i} ci,将 1 ∼ 2 n 1\sim 2n 12n 两两配对使得每一对 ( a i , b i ) (a_i,b_i) (ai,bi) ∣ c b i − c a i ∣ |c_{b_i}-c_{a_i}| cbicai 总和最小且对于任意 i i i 不存在 2 i − 1 2i-1 2i1 2 i 2i 2i 配对的情况。

1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1n105

解题思路

不考虑限制那么显然就是把 c i c_i ci 排序然后相邻的配对,如果有的配对不符合限制我们就需要置换一下,找到相邻的一个进行如下配对。
A 1 − A 2 = B 2 − B 1 A_1-A_2=B_2-B_1 A1A2=B2B1

也就是在两组相邻配对之间的 c i − c i − 1 c_i-c_{i-1} cici1 会产生两次贡献。

因为这样原本的配对是不合法的,所以新的配对肯定是合法的,然后这种隔空配对是可以几个连一起互换的,贡献相同。

所以设 f i , 0 / 1 f_{i,0/1} fi,0/1 表示到 i i i ,是否有往后拉的线。

时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)

code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
#define ll long long
using namespace std;
const ll N=2e5+10;
ll n,c[N],p[N],f[N][2];
ll read(){
	ll x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
void print(ll x)
{if(x>9)print(x/10);putchar(x%10+'0');return;}
bool cmp(ll x,ll y)
{return c[x]<c[y];}
ll cut(ll x)
{return 2*(c[p[x*2]]-c[p[x*2-1]]);}
signed main()
{
	n=read();
	for(ll i=2;i<2*n+2;i++)c[i]=read(),p[i]=i;
	sort(p+2,p+2*n+2,cmp);
	//0 �� 
	//1 �������
	ll sum=0;
	for(ll i=1;i<=n;i++)sum+=c[p[i*2+1]]-c[p[i*2]];
	f[0][1]=1e18;
	for(ll i=1;i<=n;i++){
		if((p[i*2]^1)==p[i*2+1]){
			f[i][0]=f[i-1][1]+cut(i);
			f[i][1]=min(f[i-1][0],f[i-1][1]+cut(i));
		}
		else{
			f[i][0]=min(f[i-1][0],f[i-1][1]+cut(i));
			f[i][1]=min(f[i-1][0],f[i-1][1]+cut(i));
		}
	}
	print(sum+f[n][0]);putchar('\n');
	return 0;
}

F-Fortune over Sportsmanship

题目大意

懒得转译了
https://codeforces.com/gym/104114/problem/F

解题思路

最小生成树,很神奇吧

code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
using namespace std;
const int N=1050;
struct node{
	int x,y,w;
}a[N*N],p[N];
int n,tot,fa[N],ans;
int read(){
	int x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
void print(int x)
{if(x>9)print(x/10);putchar(x%10+'0');return;}
int find(int x)
{return (x==fa[x])?x:(fa[x]=find(fa[x]));}
bool cmp(node x,node y)
{return x.w>y.w;}
int main()
{
	n=read();
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			int w=read();
			if(i<j)a[++tot]=(node){i,j,w};
		}
	sort(a+1,a+1+tot,cmp);int m=0;
	for(int i=1;i<=tot;i++)
		if(find(a[i].x)!=find(a[i].y)){
			if(find(a[i].x)>find(a[i].y))swap(a[i].x,a[i].y);
			ans+=a[i].w;
			p[++m]=(node){find(a[i].x),find(a[i].y)};
			fa[find(a[i].y)]=find(a[i].x);
			if(m==n-1)break;
		}
	print(ans);putchar('\n');
	for(int i=1;i<=m;i++)
		print(p[i].x),putchar(' '),
		print(p[i].y),putchar('\n');
	return 0;
}

H-Hanoi【构造】

题目大意

汉诺塔,但是第一个塔不需要按照从大到小的顺序。

然后给出初始时第一个塔上盘的顺序,要求构造一种在最终第三个塔排好序的方案,次数不超过 2 × n 2 2\times n^2 2×n2

1 ≤ n ≤ 500 1\leq n\leq 500 1n500

解题思路

考虑一种类似插入排序的方法,我们每次把塔顶前面按顺序的全丢到第三个塔,然后把第一个塔剩下的第一个丢到第二个塔,然后再把第三个塔的一个一个丢回第一个塔,第二个塔上的在合适的顺序插入这个过程。

这样每次不超过 2 n 2n 2n 可以进行一次插入排序,次数够用

code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
#include<vector>
#define mp(x,y) make_pair(x,y)
using namespace std;
const int N=510;
int n,a[N];
vector<pair<int,int> >e;
int read(){
	int x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
void print(int x)
{if(x>9)print(x/10);putchar(x%10+'0');return;}
void write(){
	print(e.size());putchar('\n');
	for(int i=0;i<e.size();i++)
		print(e[i].first),putchar(' '),print(e[i].second),putchar('\n');
	return;
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int t=1;t<=n;t++){
		int c=0;
		for(int i=n;i>=1;i--){
			if(i==1){e.push_back(mp(1,3));write();return 0;}
			if(a[i]>a[i-1])e.push_back(mp(1,3));
			else {c=i;break;}
		}
		e.push_back(mp(1,3));
		e.push_back(mp(1,2));bool flag=0;
		for(int i=c;i<=n;i++){
			if(a[i]>a[c-1]&&!flag)
				e.push_back(mp(2,1)),flag=1;
			e.push_back(mp(3,1));
		}
		if(!flag)
			e.push_back(mp(2,1));
		sort(a+c-1,a+1+n);
	}
	return 0;
}

N-Nusret Gökçe

题目大意

一个长度为 n n n 的序列,你可以给数字加 1 1 1 ,要求操作次数最少使得相邻的数字相差不超过 m m m

1 ≤ n ≤ 1 0 5 , 0 ≤ m ≤ 1 0 9 1\leq n\leq 10^5,0\leq m\leq 10^9 1n105,0m109

解题思路

直接按照最低限度加,一个地方加了之后可能会更新周围的,用一个优先队列存一下就好了。

时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)

code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cctype>
#define ll long long
#define mp(x,y) make_pair(x,y)
using namespace std;
const ll N=1e5+10;
ll n,m,a[N];
priority_queue<pair<ll,ll> > q;
ll read(){
	ll x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
void print(ll x)
{if(x>9)print(x/10);putchar(x%10+'0');return;}
//bool cmp(ll x,ll y)
//{return (a[x]==a[y])?(a[x]>a[y]):(x<y);}
signed main()
{
	n=read();m=read();
	for(ll i=1;i<=n;i++)a[i]=read(),q.push(mp(a[i],i));
	while(!q.empty()){
		ll x=q.top().second;
		if(q.top().first!=a[x]){q.pop();continue;}
		q.pop();
		a[0]=a[n+1]=a[x];
		ll w=max(a[x-1]-m,a[x+1]-m);
		if(w>a[x]){
			a[x]=w;
			if(x>1)q.push(mp(a[x-1],x-1));
			if(x<n)q.push(mp(a[x+1],x+1));
		}
	}
	for(ll i=1;i<=n;i++)print(a[i]),putchar((i==n)?'\n':' ');
	return 0;
}