8.1日考试记录
机房模考:2019.8.1
题目有点小难,老师原话:第一题NOIPTG压轴,第二题超出NOIP(黑题233),第三题TGD1T3(蓝题)
23333333333333333333333333333333333333333333
作为一个完美主义者菜的抠jio还想打正解,没打出一个暴力的我成功的拿到了爆零的好成绩2333
"爆零前夕,离世界末日还有17分钟。
本蒟蒻看着一个暴力都没打出来的文件夹,颤巍巍地开始打考试记录"
(写于考场)
这就叫做绝望
不按编号顺序来给出分析了吧,难度从小到大
3.电路维修 (cir.pas/c/cpp/in/out,1s,64MB)
这道题是经典的广搜优化题。
题目在很多地方找得到,比如一本通提高篇P46广搜优化技巧中例题就是
本人由于有这个印象在考场上面翻了一下233
但是我还是没写它。
不知道是脑抽还是什么,知道这道题的做法之后感觉…就有点不敢做了毕竟不能翻书的
这就直接导致本来直接就起手这题的话就算没翻到原题,应该也是做得出的情况下,我直接爆零233
正解就是双端队列优化的广搜(相当于跑最短路),需要转一下的路的权值为一,不用转的为零,最短路跑就好了
好像不是很难嘛
标程:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<stdlib.h>
#include<algorithm>
#include<queue>
#define X first
#define Y second
using namespace std;
#define fr(i,n) for(int i=0;i<n;i++)
#define fo(i,n) for(int i=1;i<=n;i++)
#define fe(i,n) for(__typeof(n.begin()) i=n.begin();i!=n.end();i++)
typedef pair<int,int>ii;
deque<ii>q;
int d[520][520];//浪漫的数组大小
char s[520][520];
void push(int x,int y,int z,int w)
{
if(d[x][y]>z+w)
{
d[x][y]=z+w;
if(w)
q.push_back(ii(x,y));
else
q.push_front(ii(x,y));
}
}
int t,n,m;
int main()
{
freopen("cir.in","r",stdin);freopen("cir.out","w",stdout);
for(scanf("%d",&t);t--;)
{
scanf("%d %d",&n,&m);
for(int i=0;i<n;i++)
scanf("%s",s[i]);
if(n+m&1)
{
puts("NO SOLUTION");
continue;
}
memset(d,0x3f,sizeof d);
d[0][0]=0;
q.push_back(ii(0,0));
while(q.size())
{
ii u=q.front();
q.pop_front();
if(u.X>0&&u.Y>0)
push(u.X-1,u.Y-1,d[u.X][u.Y],s[u.X-1][u.Y-1]!='\\');
if(u.X>0&&u.Y<m)
push(u.X-1,u.Y+1,d[u.X][u.Y],s[u.X-1][u.Y]!='/');
if(u.X<n&&u.Y>0)
push(u.X+1,u.Y-1,d[u.X][u.Y],s[u.X][u.Y-1]!='/');
if(u.X<n&&u.Y<m)
push(u.X+1,u.Y+1,d[u.X][u.Y],s[u.X][u.Y]!='\\');
}
printf("%d\n",d[n][m]);
}
return 0;
}
里面有不少很有意思的写法:
比如在对无解的判断中,很明显只有横纵坐标奇偶性相同的点能够互达,而不同的就直接判断无解。
对pair数对的运用中,使用define让它看起来更像结构体,用起来更自然
其它的基本就属于spfa的跑法了,然后因为如果一个点比较近,它更新到接近最优解得可能性大,所以把它放队首,而太远的放队尾。优化度玄学
1.磁力阵 (mag.pas/c/cpp/in/out,1s,64MB)
题目描述
公元5780 年,随着巨大的能量注入太阳,太阳终于重新开始了正常运转。人类文明史
上最大的灾难总算是过去了,地球也迎来了崭新的黎明。
伴随着这个令人振奋的时刻,一个新的问题摆在了人们面前,那就是大灾难之后的重建
工作。通信部门的主管Schnessturm 知道,当务之急就是恢复全球通信,其中最重要的任务
就是重新制造并发射Zweihundertfünfzig 通信卫星。于是他向设施管理处的Satellit 提出了他
的看法。
“ Schnessturm 先生,我完全同意您的看法。但是Zweihundertfünfzig 通信卫星需
要修改设计。根据我的调查,五年前卫星与我们意外失去联络到最终坠落,很大的原因就是
因为设计问题。”Satellit 说。
“那 Satellit 先生,能不能具体解释一下是什么问题呢?”Schnessturm 说。
“具体来说是这样 Schnessturm 先生。通信卫星内部的一个重要元件被称为‘
阵’。如下面的图像所示,磁力阵是一 N N 的方阵,而方阵的边上是电磁铁。电磁铁按
照图中的方式连续顺序编号,有些电磁铁可能已经被移除。我们发现,如果存在一个正方形,
它的边上全部都有电磁铁的话,这个磁力阵就是不稳定的。而Zweihundertfünfzig 的磁力阵
就是不稳定的。我们需要移除最少数目的电磁铁使之达到稳定才行。”
于是 Schnessturm 和Satellit 希望你能够帮助他们解决这个问题,以尽快恢复全球通信。
别忘了,你可是科学院的顶级信息专家!
输入格式
输入文件包含多组测试数据。第一行包含整数T,表示测试数据的组数。
每组测试数据由两行组成。第一行包含一个整 N,表示方针的长度。
第二行包含 K 和K 个整数A1, A2 K,表示编号为A1, A2 K 的电磁铁已经
被移除。同一编号的电磁铁不会出现两次。
输出格式
对于每组测试数据,在单独的一行内输出答案。
样例输入
2
2
0
3
3 12 17 23
样例输出
3
3
样例说明
第二组测试数据如下图所示。只需要再移除灰色的电磁铁就可以
数据范围与约定
对于20% 的数据,满足N=2。
对于 100% 的数据,满足2≤N≤5,0≤K≤2N(N + 1),T≤10
这题目难度主要在于判断正方形和转换编号和状态上面,所以要做很多的集合运算(边的集合),位运算也就显得尤其重要
先贴标程:
#include<stdio.h>
#define _(x) (1LL)<<(x-1)//由编号到位
int dmx;
int n,m;
long long k;
long long nd[60];
long long sq[10][10];
int sc;
int _1(int x,int y)//坐标到边的编号
{//第i行第j列上面那条边的编号
return (2*n+1)*(x-1)+y;
}
int _2(int x,int y)//坐标到边的编号
{//第i行第j列左边那条边的编号
return _1(x,y)+n;
}
void __()
{
int i,j,k,l,m;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
sq[i][j]=_(_1(i,j))|_(_1(i+1,j))|_(_2(i,j))|_(_2(i,j+1));
//第i行第j列的正方形的状态。
//即四条边的编号位置为一的64位数字
nd[sc++]=sq[i][j];
}
for(k=2;k<=n;k++)
for(i=1;i+k-1<=n;i++)
for(j=1;j+k-1<=n;j++)
{
nd[sc]=0;
for(l=i;l<i+k;l++)
for(m=j;m<j+k;m++)
nd[sc]^=sq[l][m];
sc++;
}
}
int ___(long long u,int s)
{
int i,h=0;
long long dsr=0,t=u;
for(i=0;i<sc;i++)
{
if((t&nd[i])==nd[i])
{
h++;
t^=nd[i];
if(dsr==0)
dsr=nd[i];
}
}
if(h==0)
return 1;
else if(s+h>dmx)
return 0;
for(i=1;i<=m;i++)
if((dsr&_(i))&&___(u^_(i),s+1))
return 1;
return 0;
}
int t,dps,x;
int main()
{
freopen("mag.in","r",stdin);
freopen("mag.out","w",stdout);
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
m=2*n*(n+1);
sc=0;
__();
k=((1LL)<<m)-1;
scanf("%d",&dps);
while(dps--)
{
scanf("%d",&x);
k^=_(x);
}
for(dmx=0;!___(k,0);dmx++);
printf("%d\n",dmx);
}
return 0;
}
他的函数名好骚啊
写法很奇特
在代码里面有一些注释了,但并不完善(引用代码里面的注释看不清就不在里面继续加了)
一杠函数
这个函数并不是一个严格的函数,只是用define宏定义了个集合操作。
如果将一个数在不在集合里看做这个数在二维状态中对应数位是否为一的话,那么它就是指将x放进一个空集,创造一个只有x一个元素的集合。
这个主要是用于往某个集合里添加元素(先搞一个只有这个元素的集合再求并集就好了)
杠一函数
可以看到注释。
因为每个正方形都对应着一个编号四条边,仔细看横着的边的规律,可以发现其实每个正方形(坐标
x
,
y
x,y
x,y)的上边就是对应着
(
2
∗
n
+
1
)
∗
(
x
−
1
)
+
y
(2*n+1)*(x-1)+y
(2∗n+1)∗(x−1)+y这个编号。
所以这个相当于找到某一条横边
(找下边就可以通过下面那个正方形的上边去找)
杠二函数
其实和杠一是同一性质的,适用于求竖边,返回正方形x,y的右边的编号。
二杠函数
一个初始化函数,主要是将所有正方形(大的小的胖的瘦的)全都给塞到nd数组中。
sq中存的是最小的正方形,用于合成大的正方形。
第一个for嵌套中,枚举每个小正方形的左上坐标,先初始化所有的小正方形,并将它的四条边通过集合的形式保存(即将这四条边放入一个集合,并将这个集合保存于nd和sq中作为正方形的状态)。
在第二个for嵌套中,枚举所有正方形的右上坐标和边长k,并通过k^2个小正方形去合成一个边长为k的大正方形,全都放入nd中,其中异或的技巧是既可以将没在集合中的边加入又可以将重复的边删除。
比如一个2*2的矩阵(如下)
它由四个小正方形组成,但是由于我们要把这个矩阵转化为正方形,所以要把矩阵中的边给删除。
显而易见,四个小正方形组合的话中间需要删除四条边会重复出现,所以我们只要在合并的基础上去重就ok了。
在位运算中,我们存的正方形状态(四条边),如果重复,1 x o r ( 异 或 ) xor(异或) xor(异或) 1会变成0,即边被消除。如果没有重复,1 x o r xor xor 0为1,即加入改变。方便地合成了大正方形。
简直妙哇
三杠函数
程序主体,迭代加深dfs。
首先变量分别的含义:
d s r dsr dsr指第一次找到的正方形, h h h是还剩下的正方形个数,用于判断是否终止。 u u u为目前处理的状态, s s s是已经用的步数, t t t为临时变量,用于防止对 u u u的检查带来的对 u u u的改变
首先第一个 f o r for for嵌套中,
第一个 i f if if指的是判断集合 n d [ i ] nd[i] nd[i](一个正方形)是否包含于 t t t(目前状态)中。在这里面 &( a n d and and(且)) 用于求交集。众所周知,如果A交B等于B,则B包含于A。这种写法也可以用 |( o r or or(与))来实现,即A并B等于A,则B包含于A。
当这个正方形包含于原图形,则 h h h统计数加一,在 t t t中删去 n d [ i ] nd[i] nd[i]这个正方形(异或即求补集)。并当找到的正方形是第一个,记录在 d s r dsr dsr中。
很明显,当 h h h为零时,我们已经将所有的正方形处理完,可以直接返回层数可行的信息。
而其他情况,比如当前没有处理完,但次数加上最少删去次数(即h的值,每个正方形至少需要一次)已经超过了迭代的层数了,可以剪枝。
那如果都没有,就一条一条边的扫。如果这条边是目前 d s r dsr dsr中的一条边,那么将这条边删除,次数加一,继续扫下去
主函数
剩下的其实很简单了,就是输入输出和迭代加深的常规写法。至于输入删边,也就是将初始的一个全集k中,异或输入的边的状态即可。
之间还有一个细节,所有的边数 m m m可以O(1)用 2 ∗ n ∗ ( n + 1 ) 2*n*(n+1) 2∗n∗(n+1)的公式算出。之后在扫边的时候有用
2. 虫洞
"其实关于这题,我先想到的是连边写最短路
但一看数据,本来以为可以写出来一个接近正解的搜索,结果无限写挂(这tm就是卡住我的主要原因啊!!!!!)
由于在没有通过虫洞跳到其它虫洞时,平走是唯一选项,所以实际上就算是大暴力也可以写成2^P,而且实际上剪枝的空间很多很多,即使P=40,应该都可以勉强卡过。
但是本题细节确实很多,
首先,有可能浪费一点步数进了一个虫洞,却不一定能得到比不浪费这些步数(即直接走过去)更优的结果。
而且,向前穿的虫洞应不应该走的剪枝,应该要用到二分查找才能达到最大效果,因为向前走时难以知道下一步走到哪(其实可以用链表但是本人傻了)。所以优化极难的233"
(写于考场)
没错我就是起手这题。
虽然在考试时写的有一点bug,但是还是可以原谅,这题难度是真的过分。
起手这题!!!!!!!!!!!
爆零的罪魁祸首
因为当时讲课的时候我早退了一会,所以有些细节没听到,标程挂上:
#include<bits/stdc++.h>
using namespace std;
#define fr(i,n) for(int i=0;i<n;i++)
#define fo(i,n) for(int i=1;i<=n;i++)
#define fe(i,n) for(__typeof(n.begin()) i=n.begin();i!=n.end();i++)
int l[100],c;
int x[50],y[50];
int d[100][100];
int w,s,p;
set<int>se;
int F(int b,int e)
{
if(b==e)
return 0;
if(se.count(b))
return 0x3fffffff;
int f=e;
for(int i=0;i<p;i++)
{
if(b<x[i]&&x[i]<f&&(x[i]-b)%s==0)
f=x[i];
}
while(f!=e&&se.count(f))
f--;
if(f==b)
return 0x3fffffff;
return (f-b+s-1)/s+F(f,e);
assert(0);
}
int Q(int x)
{
return lower_bound(l,l+c,x)-l;
}
int main()
{
freopen("wormhole.in","r",stdin);freopen("wormhole.out","w",stdout);
while(scanf("%d %d %d",&w,&s,&p),w)
{
se.clear();
c=0;
for(int i=0;i<p;i++)
{
scanf("%d %d",x+i,y+i),se.insert(x[i]);
l[c++]=x[i],l[c++]=y[i];
}
l[c++]=0,l[c++]=w;
sort(l,l+c);
c=unique(l,l+c)-l;
memset(d,0x3f,sizeof d);
for(int i=0;i<p;i++)
d[Q(x[i])][Q(y[i])]=0;
for(int i=0;i<c;i++)
for(int j=i+1;j<c;j++)
d[i][j]=min(d[i][j],F(l[i],l[j]));
for(int k=0;k<c;k++)
for(int i=0;i<c;i++)
for(int j=0;j<c;j++)
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
printf("%d\n",d[0][c-1]);
}
return 0;
}
思路其实就是一个最短路,但是关键实现难度在于连边和F函数算距离。
我考试时便被这东西卡的死死的而且一卡就没有松了
离散化处理,只有虫洞点需要单独一个个处理好。而其它点可以通过F计算距离略过。
代码里面有很多华丽的写法比如去重的stl:unique等。由于我自己都不敢说完全懂了,所以不解释。
懒是本质,菜是原罪
总而言之,这次考试是真的难受。在我们NOIP机房里,有分前十。
其实拿分不难第一题可以打表20/笑哭,但是模考嘛,于是很多大佬卡死在了追求AC的梦想,没有尝试骗分。
除了我这个蒟蒻是真的菜的抠jio
总而言之,题目很有价值。只要不是正式考试,错的多,就是进步空间大。好好分析考试,争取进步!!!