HGOI-国庆七连测-day2

题解

其实今天的题目不是很难…但是内存炸了第一题就爆零了orz

在这里插入图片描述


第一题——八数码

【题目描述】

  • 给出你两个九宫格包含数字0-8,每次操作能够将0进行上下左右某个方向上的交换,问最少多少次交换到目标的情况。达不到就输出-1。

  • emmm其实是一道万年老题,当年学bfs的时候打过一次,关键在于去重,hash压缩之后进行bfs。
  • 但是我忘记开map了,而是本机测的时候开了一个 9 1 0 8 9*10^8 的布尔数组,就差不多3个G吧…

#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
const int N=410000;
void fff(){
	freopen("eight.in","r",stdin);
	freopen("eight.out","w",stdout);
}
int sta[9],tar[9],ta;
map<int,int>mp;
struct que{
	int a[N],ss[N];
	int head,tail;
	void init(){
		memset(a,0,sizeof(a));
		head=tail=0;
	}
	bool empty(){
		return head==tail;
	}
	void push(int x,int step){
		tail=(tail+1)%N;
		a[tail]=x;
		ss[tail]=step;
	}
	int top_a(){
		return a[head+1];
	}
	int top_step(){
		return ss[head+1];
	}
	void pop(){
		head++;
		head%=N;
	}
}q;
inline int HASH(int *a){
	int x=a[1];
	for(int i=2;i<=9;i++){
		x=x*10;
		x+=a[i];
	}
	return x;
}
int main(){
//	fff();
	for(int i=1;i<=9;i++) scanf("%d",&sta[i]);
	for(int i=1;i<=9;i++) scanf("%d",&tar[i]);
	int tt=HASH(sta);
	ta=HASH(tar);
	q.push(tt,0);
	while(!q.empty()){
		int t1=q.top_a(),t2=q.top_step();q.pop();
		if(t1==ta){
			printf("%d",t2);
			return 0;
		}
		if(mp[t1]) continue;
		mp[t1]++;
		int pos;
		for(int i=9;i>=1;i--){
			sta[i]=t1%10;
			t1/=10;
			if(sta[i]==0) pos=i;
		}
		if(pos>3){swap(sta[pos],sta[pos-3]);q.push(HASH(sta),t2+1);swap(sta[pos],sta[pos-3]);}
		if(pos<=6){swap(sta[pos],sta[pos+3]);q.push(HASH(sta),t2+1);swap(sta[pos],sta[pos+3]);}
		if(pos%3!=0){swap(sta[pos],sta[pos+1]);q.push(HASH(sta),t2+1);swap(sta[pos],sta[pos+1]);}
		if(pos%3!=1){swap(sta[pos],sta[pos-1]);q.push(HASH(sta),t2+1);swap(sta[pos],sta[pos-1]);}
	}
	puts("-1");
}

第二题——多段线性函数

【题目描述】

  • 给出函数 f [ y ] = i = 1 n y x i f[y]=\sum_{i=1}^{n}{ \left| y-x_i \right|} 以及n个x的取值范围 [ l i , r i ] [l_i,r_i] ,求出使得函数求出最小值的y的区间。
  • 可证明y的区间只有一段,并且当y只能取一个值z时, L = R = z L=R=z

  • 其实这道题数学建模之后题目就很好理解了,就是给出n段x的区间,x在每段区间上可以随便取,然后在数轴上取一个点y,到这写点的举例最少。

  • 大概就是长下面这样:
    红点是各个x的位置

  • 然后你就会发现函数其实就是一个区间距离函数了,可以改写了:
    f [ x ] = i 1 n { y r i r i &lt; y 0 l i y r i l i y l i &gt; y f[x]=\sum_{i-1}^{n}\begin{cases} y-r_i &amp; r_i&lt;y \\ 0 &amp;l_i\leq y \leq r_i \\ l_i-y &amp; l_i&gt; y\\ \end{cases}

  • 然后你就会发现这个函数好像是一个单峰函数,还有可能是个平底锅…

  • 然后果断两次二分枚举(一次是 &lt; &lt; ,一次是 \leq )左右端点就可以了。

  • 复杂度貌似是 O ( n l o g 2 ( r l ) ) O(nlog_2(r-l))


#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
void fff(){
	freopen("linear.in","r",stdin);
	freopen("linear.out","w",stdout);
}
const int N=1e5+10;
int n;
struct node{
	int l,r;
	bool operator <(const node x) const{
		if(l==x.l) return r<x.r;
		return l<x.l;
	}
}a[N];
inline int read(){
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x;
}
inline int get_fy(int x){
	int s=0;
	for(int i=1;i<=n;i++){
		if(a[i].r<x) s+=(x-a[i].r);
		if(a[i].l>x) s+=(a[i].l-x);
	}
	return s;
}
int ans1,ans2;
int main(){
	fff();
	n=read();
	int ll=1e9,rr=0;
	for(int i=1;i<=n;i++){
		a[i].l=read(),a[i].r=read();
		ll=min(a[i].l,ll);
		rr=max(a[i].r,rr);
	}
	int l=ll,r=rr;
	while(l<=r){
		int mid=(l+r)>>1;
		int t1=get_fy(mid-1),t2=get_fy(mid);
		if(t1>t2)l=(ans1=mid)+1;
		else r=mid-1;
	}
	l=ll,r=rr;
	while(l<=r){
		int mid=(l+r)>>1;
		int t1=get_fy(mid),t2=get_fy(mid+1);
		if(t2>t1) r=(ans2=mid)-1;
		else l=mid+1;
	}
	cout<<ans1<<' '<<ans2;
}


第三题——模模塔

【题目描述】

  • 给出数列 a [ 1.. n ] a[1..n] b [ 0.. n 1 ] b[0..n-1] ,求出数列 c [ 1.. n ] c[1..n]
  • 给出函数 c i = j = 1 i a i j b i &ThinSpace; m o d &ThinSpace; j c_i=\sum_{j=1}^{i}{a_{\lfloor \frac{i}{j} \rfloor}b_{i \, mod\,j} }

  • 其实我这道题真的只会暴力orz,std给的玄学分块其实是把这个函数强行优化到 O ( n n ) O(n\sqrt n) ,所以接下来可以慢慢来看这个表演…
  • 考虑对于 i j \lfloor \frac{i}{j} \rfloor 进行分块,看的出来相同的 i j \lfloor \frac{i}{j} \rfloor j j 是连续的那么b就是这些下表的等差数列orz…然后在 j i j\leq \sqrt i 的范围,就可以直接进行暴力赋值,复杂度是 O ( n n ) O(n\sqrt n)
  • 然后对于 j &gt; i j&gt; \sqrt i 的数据来说,预处理出 f [ i ] [ k ] f[i][k] 表示的从 i i 开始的往前公差为 k k 的前缀和… O ( 1 ) O(1) 查询。复杂度为 O ( n n ) O(n\sqrt n)
  • 所以总的复杂度还是 O ( n n ) O(n \sqrt n) ,印证了那一句“世界上只有两种算法的话”

#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long
using namespace std;
void fff(){
	freopen("mmt.in","r",stdin);
	freopen("mmt.out","w",stdout);
}
inline int read(){
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x;
}
const int K=330; 
const int N=100010;
const int MOD=123456789;
int n;
int a[N],b[N],c[N];
int sum[K][N];
int main(){
//	fff();
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=n;i++)b[i-1]=read();
	for(int i=1;i<K;i++)
		for(int j=0;j<n;j++){
			if(j>=i) sum[i][j]=sum[i][j-i];
			sum[i][j]=(sum[i][j]+b[j])%MOD;
		}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=K&&j<=i;j++)
			c[i]=(c[i]+(LL)a[i/j]*b[i%j])%MOD;
		for(int l=K+1,r=K+1;l<=i;l=r+1){
			int t=i/l;r=i/t;
			c[i]=(c[i]+(LL)a[t]*(MOD+sum[t][i-t*l]-(i>t*(r+1)?sum[t][i-t*(r+1)]:0)))%MOD;
		}
	}
	for(int i=1;i<=n;i++) printf("%d\n",c[i]);
}

猜你喜欢

转载自blog.csdn.net/qq_42037034/article/details/82927088