HGOI-国庆七连测-day2

题解

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

在这里插入图片描述


第一题——八数码

【题目描述】

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

  • emmm其实是一道万年老题,当年学bfs的时候打过一次,关键在于去重,hash压缩之后进行bfs。
  • 但是我忘记开map了,而是本机测的时候开了一个 9 ∗ 1 0 8 9*10^8 9108的布尔数组,就差不多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|} f[y]=i=1nyxi以及n个x的取值范围 [ l i , r i ] [l_i,r_i] [li,ri],求出使得函数求出最小值的y的区间。
  • 可证明y的区间只有一段,并且当y只能取一个值z时, L = R = 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} f[x]=i1nyri0liyri<yliyrili>y

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

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

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


#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] a[1..n] b [ 0.. n − 1 ] b[0..n-1] b[0..n1],求出数列 c [ 1.. n ] 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} } ci=j=1iajibimodj

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

#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]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值