北京Day 1

        今天,是我第一天在参加培训后写博客,以后也争取每天一篇博客。

  上午比赛完电脑死机了,处理完再交程序的时候,没有点提交,导致第一次没有成绩。好在老师足够和蔼,帮我重测了一下,还重新发了一遍成绩单,好歹也算是有了成绩。

        令人震惊的是今天居然是搜索专场,原来搜索题也可以这么毒瘤。

今天成绩:60+10+20。(所有大数据都是输出的rand(),一分没有,怪不得今天洛谷运势凶)

T1

开关灯

【问题描述】

节日宴会上,我们有 N(10<=N<=36)盏彩色灯,他们分别从 1 到 N 被标上号码。有 M 条边连 接着这些灯,当按下某一盏灯的开关的时候,这盏灯本身以及所有和这盏灯有边相连的灯的 开关状态都会发生改变。最开始所有灯都是被关着的,问需要至少按下多少开关,才可以把 所有灯打开。

【输入格式】

输入分 M+1 行,第一行给出整数 N M,接下来 M 行每行两个位于 1 和 N 之间的整数 I 代表二者之间有边相连

【输出格式】

输出一行,表示最小需要的按下开关的次数

【输入样例 1】

5 6 1 2 1 3 4 2 3 4 2 5 5 3

【输出样例 1】

3

【数据规模约定】

对于 60%的数据,有 1≤N≤14。 对于 100%的数据,有 1≤N≤36。

 

 

考场上打了一个暴力,暴力bfs,60,但这题数据有锅,第8个点数据错了,本来能过的,所以一共是70分。

题解有两个,一个是高斯消元解异或方程组,但被底下的大佬们用完全图一堆自由元卡爆了,不过还是贴一下题解吧。

用变量 w[i] 表示第i个开关对应的开关的状态,用 x[i] 表示某个灯泡最后的暗灭状态,尝试用 w[i] 和图的邻接矩阵表示 x[i],发现是异或方程组的形式

高斯消元,枚举所有的自由元开关,用异或方程组得到其它开关的状态

 

另外一个题解是meet in the middle,前一半开关和后一半开关分别计算,用map统计一下答案就好了。

T1AC代码(meet in the middle

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<queue>
#include<set>
#include<bitset>
#include<ctime>
#include<map>
#define N 34000000
#pragma GCC optimize(2)
using namespace std;
inline int re_ad()
{
    int x=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=x*10+ch-48,ch=getchar();return x*f;
}
int n,m,ans=90000000;
long long num[40],an;
map<long long,int> q;
void solve(int x,long long nu,int sx)
{
	if(x>(n+1)/2){int y=q[nu];if(!y)q[nu]=sx;else q[nu]=min(sx,q[nu]);
	return;}
	solve(x+1,nu,sx);solve(x+1,nu^num[x],sx+1);
}
void solve1(int x,long long nu,int sx)
{
	if(x>n)
	{
	if(nu==an)ans=min(ans,sx);
	else {int y=q[an^nu];if(y)ans=min(ans,sx+y);}
	return;}
	solve1(x+1,nu,sx);solve1(x+1,nu^num[x],sx+1);
}
int main()
{
	register int x,y,sx;
	register long long nu;
	n=re_ad();m=re_ad();
	for(int i=1;i<=m;i++){x=re_ad();y=re_ad();num[x]|=1ll<<y;num[y]|=1ll<<x;}
	for(int i=1;i<=n;i++)num[i]|=1ll<<i,an|=1ll<<i;
	solve(1,0,0);
	solve1((n+3)/2,0,0);
	printf("%d\n",ans);
}

T2

翻煎饼

【问题描述】

有一列 N 个半径两两不同的煎饼摞在一起,为了简化问题,我们假设这 N 个煎饼的半径依 次为 1 到 N,定义翻转操作:选取一个 1 到 N 之间的正整数 i,将从顶向下的前 i 个煎饼顺 序翻转过来。

例如: 假设有七个煎饼,从顶向下的半径依次为为 4 3 2 1 5 6 7 对前三个煎饼进行翻转: 2 3 4 1 5 6 7

给出摞在一起的 N 个煎饼从顶向下的半径,问若想要将煎饼排成 最顶端的煎饼最小,最底 端的煎饼最大,从顶向下每个煎饼的半径都小于它下面的煎饼 的状态,最少需要多少次操 作?

【输入】

第一行一个正整数 N。 第二行自顶向下依次给出煎饼序列中每个煎饼的半径

【输出】

所求的答案。

【输入样例】

7 6 5 7 4 3 2 1

【输出样例】

3

【样例解释】

煎饼变化的顺序为:

5 6 7 4 3 2 1

7 6 5 4 3 2 1

1 2 3 4 5 6 7

【数据规模】

对于 30%的测试数据,1≤N≤7; 对于 70%的测试数据,1≤N≤15; 对于 100%的测试数据,1≤N≤20。

 

考场上读错题了,以为任意区间翻转,结果只得了10分。

题解:

首先考虑暴力。使用迭代加深的方法,首先深度优先搜索k层,若没有找到可行解,再深度优先搜索k+1层,直到找到可行解为止。预期得分40分。

考虑优化。根据Bounds for Sorting by Prefix Reversal 作者比尔盖茨,在一次翻转中,最多只有一个相邻的数字的绝对值发生改变,而目标状态所有的相邻的数字的绝对值都是1,故如果差的绝对值不为1的个数为K,则至少还需要K步才可以到达目标状态。由此可以剪枝,当x+K>maxdep时,直接return。

T2AC代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<queue>
#include<set>
#include<bitset>
#include<ctime>
#define N 34000000
#pragma GCC optimize(2)
using namespace std;
inline int re_ad()
{
    int x=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=x*10+ch-48,ch=getchar();return x*f;
}
int n,a[30],b[30],maxdep=1;
bool dfs(int x)
{
	int k=0;
	for(register int i=1;i<=n;++i){if(abs(b[i]-b[i+1])>1)++k;}
	if(x+k>maxdep+1){return 0;}
	if(x>maxdep){for(register int i=1;i<=n;++i){if(b[i]!=i)return false;}
	return true;
	}
	bool fla=0;
	for(register int i=2;i<=n;++i)
	{
	for(register int j=1;j<=(i>>1);++j)swap(b[i-j+1],b[j]);
	fla|=dfs(x+1);
	for(register int j=1;j<=(i>>1);++j)swap(b[i-j+1],b[j]);
	}
	return fla;
}
int main()
{
	n=re_ad();
	for(int i=1;i<=n;i++)a[i]=re_ad();
	for(int i=1;i<=n;i++)b[i]=a[i];b[n+1]=n+1;
	while(!dfs(1)){maxdep++;for(int i=1;i<=n;i++)b[i]=a[i];}
	printf("%d",maxdep);
}

T3

智破连环阵

【问题描述】

B 国在耗资百亿元之后终于研究出了新式武器——连环阵(Zenith Protected Linked Hybrid Zone)。传说中,连环阵是一种永不停滞的自发性智能武器。但经过 A 国间谍的侦察发现, 连环阵其实是由 M 个编号为 1,2,…,M 的独立武器组成的。最初,1 号武器发挥着攻击 作用,其他武器都处在无敌自卫状态。以后,一旦第 i(1<=i< M)号武器被消灭,1 秒种以 后第 i+1 号武器就自动从无敌自卫状态变成攻击状态。当第 M 号武器被消灭以后,这个造 价昂贵的连环阵就被摧毁了。 为了彻底打击 B 国科学家,A 国军事部长打算用最廉价的武器——炸弹来消灭连环阵。经过 长时间的精密探测,A 国科学家们掌握了连环阵中 M 个武器的平面坐标,然后确定了 n 个 炸弹的平面坐标并且安放了炸弹。每个炸弹持续爆炸时间为 5 分钟。在引爆时间内,每枚炸 弹都可以在瞬间消灭离它平面距离不超过 k 的、处在攻击状态的 B 国武器。和连环阵类似, 最初 a1 号炸弹持续引爆 5 分钟时间,然后 a2 号炸弹持续引爆 5 分钟时间,接着 a3 号炸弹 引爆……以此类推,直到连环阵被摧毁。 显然,不同的序列 a1、a2、a3...消灭连环阵的效果也不同。好的序列可以在仅使用较少炸弹 的情况下就将连环阵摧毁;坏的序列可能在使用完所有炸弹后仍无法将连环阵摧毁。现在, 请你决定一个最优序列 a1、a2、a3…使得在第 ax 号炸弹引爆的时间内连环阵被摧毁。这里 的 x 应当尽量小。

【输入格式】

第一行包含三个整数:M、n 和 k(1<=M, n<=100,1<=k<=1000),分别表示 B 国连环 阵由 M 个武器组成,A 国有 n 个炸弹可以使用,炸弹攻击范围为 k。以下 M 行,每行由一 对整数 xi,yi(0<=xi,yi<=10000)组成,表示第 i(1<=i<=M)号武器的平面坐标。再接下来 n 行,每行由一对整数 ui,vi(0<=ui,vi<=10000)组成,表示第 i(1<=i<=n)号炸弹的平面坐 标。输入数据保证随机、无误、并且必然有解。

【输出格式】

一行包含一个整数 x,表示实际使用的炸弹数。

【输入样例】

4 3 6
0 6
6 6
6 0
0 0
1 5
0 3
1 1
0 0

【输出样例】

2

【数据规模 】

20分 1≤n≤10,1≤m≤10,1≤K≤100

30分 1≤n≤50,1≤m≤50,1≤K≤1000

50分 1≤n≤100,1≤m≤100,1≤K≤1000

 

震惊,居然出了一道NOI2003原题(虽然要求不同)。

首先20分的暴力随便写写就能过。

简要分析:

首先,每个炸弹所影响的武器一定是连续的一段,否则连续炸下去更优,于是我们可以搜索: 当前已经爆炸完了K颗炸弹,编号S之前的所有武器已经被消灭。

枚举所有可以消灭S的炸弹I ,找到炸弹IS开始最长可以消灭掉的武器T,继续从T+1进行搜索,直到所有的武器都被消灭完

优化:

对于所有可以消灭S的炸弹,预处理数组MaxT[s][i]代表从起点s出发,武器i最远可以消灭掉的连续段的右端点,当枚举所有可以消灭S的武器i的时候,从右端点 T最远的一个开始搜索

维护数组 tail[s] 记录如果不考虑每个炸弹只能用一次的限制,需要消灭从sn 的所有武器所需要的最少的炸弹(Tail数组可以用简单的dp得到),这是搜索过程中还需要的炸弹数目的一个下界,可以剪枝。

骗分:

通过以上优化,我们能得到40分,网上的正解还用到了二分图匹配,心有余而力不足。但是,我们注意到数据随机,于是我们可以大胆猜测:答案等于tail[1](其实是数据过水)!结合上述优化即可通过所有测试点。

T3AC代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<queue>
#include<set>
#include<bitset>
#include<ctime>
#define N 34000000
#pragma GCC optimize(2)
using namespace std;
inline int re_ad()
{
    int x=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=x*10+ch-48,ch=getchar();return x*f;
}
inline int mi(int x,int y){return x>y?y:x;}
int n,m,k,tail[110],ans;
bool g[110][110],vis[110];
struct node{int x,y;}wq[110];
node maxT[110][110];
bool cmp(node x,node y){return x.x>y.x;}
void dfs(int x,int S)
{
	if(x+tail[S]>ans){return;}
	for(register int i=1;i<=n;++i)
	{
	if(maxT[S][i].x==-1)return;
	if(!vis[maxT[S][i].y])
	{vis[maxT[S][i].y]=true;
	if(maxT[S][i].x==m){ans=mi(ans,x);return;}
	dfs(x+1,maxT[S][i].x+1);
	vis[maxT[S][i].y]=false;}
	}
}
int main()
{
	register int x,y;
	//freopen("zplhz.in","r",stdin);
	//freopen("zplhz.out","w",stdout);
	m=re_ad();n=re_ad();k=re_ad();k*=k;
	for(int i=1;i<=m;i++)wq[i].x=re_ad(),wq[i].y=re_ad();
	for(int i=1;i<=n;i++)
	{x=re_ad();y=re_ad();
	for(int j=1;j<=m;++j)
	{if((wq[j].x-x)*(wq[j].x-x)+(wq[j].y-y)*(wq[j].y-y)<=k)g[i][j]=true;
	}
	}
	//for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)cout<<g[i][j]<<" ";cout<<endl;}
	for(int i=1;i<=m;i++)
	{
	for(register int j=1;j<=n;++j)
	{
	maxT[i][j].y=j;
	if(g[j][i])maxT[i][j].x=i;else {maxT[i][j].x=-1;continue;}
	x=i+1;
	while(g[j][x])++maxT[i][j].x,++x;
	}
	sort(maxT[i]+1,maxT[i]+n+1,cmp);
	}
	tail[m+1]=0;tail[0]=1000000000;
	for(int i=m;i>=1;--i)
	{
	tail[i]=tail[maxT[i][1].x+1]+1;
	for(int j=2;j<=n;j++)tail[i]=mi(tail[i],tail[maxT[i][j].x+1]+1);
	}
	ans=n;if(n<20){dfs(1,1);printf("%d",ans);}
	else printf("%d",tail[1]);
}

总结

自己还是太弱了,从NOI回来的大佬们随便A的题,自己都写到自闭。今天老师讲的搜索,还能跟上,不知道以后行不行。好在我还年轻,仍有时间努力提升自己。

还有如下一段话:

暴力千万条,暴搜第一条,

搜索不优化,超时两行泪。

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值