2017_1226校区联考&题解
终于补完了2333......话说刚从帝都回来就考试......我还啥都不会啊,并不像某两位早就学过的巨神...
早上7点就来到了机房(好像并不早的样子),考试在Ya神刚搭起来的OJ上,我们自己的OJ估计是重建不起来了...早早做好了爆零的准备所以丝毫不怂。考试过程...T1?瞎猜了个结论,发现是错的,然后并不知道怎么做,敲了30分暴力滚去下一题。T2一眼网络流,于是开始建图,走进了一个大坑,一直在想有源汇有上下界最大流,发现死活建不出图,算了算了,暴力吧...反正早有心理准备。然后非常尴尬...并不知道怎么暴力,突然想到正确建图方法,行吧...就是个最基础的最大流嘛...敲完赶紧去看T3。呃呃...大概是数据结构?并不会,看了一下数据范围,暴力能拿8分...管他呢...敲上去再说...想了想感觉并没有任何思路,突然想到一个奇怪的线性做法(然而随手就造了一组数据把它WA掉了),不过...万一能骗到分呢...就开始在程序上挂链2333。写完以后并不知道该干什么...毕竟OI赛制。之后快到时间就交到OJ上貌似到时间才能看到分数?这时候才知道是和南校一起考的。
终于可以看成绩了...得分30+100+36=166 运气好拿到 Rank1(毕竟浩神并没有考),还有这次的std叫Amyzhi?(任神),行吧...T3没爆零就行,T2 A掉还行,T1暴力没挂还行(邢神貌似数组开小了gg)...看来心态还是很重要的啊。
一句话题解(下面会有详细题解):
T1:二分,将原有序列转成01序列,之后找规律,复杂度O(nlogn)
T2:最大流,在对原有棋盘黑白染色后,能量石再分奇数行和偶数行第二次染色
T3:线段树(或者set也可以?)先对z排序,枚举z之后放到坐标系(x,y)上就简单一些啦
详细题解:
T1:
给你一个N层的方格金字塔,自顶向下依次标号为第1到第N层。其中第i(1≤i≤N)层含有水平居中放置的2i−1个方格。金字塔每层中间的方格是垂直居中放置的。
这是一个4层的金字塔
Snuke在第N层写了一个(1,2,…,2N−1)的排列,接下来他按照以下规则填满了其他方格。
方格b中填写的整数,是方格b正下方,左下方和右下方方格中所写整数的中位数。
然而,他擦去了方格中填写的数字。现在他只记得第N层中他所填的那个排列(a1,a2,…,a2N−1)。请你求出第1层的数字。
这道题和河北16年省选D1T2(BZOJ4552)很像,不过我是一点都没有想到...在考场上最多也就是30分暴力了吧。
先二分出一个mid,之后把读入的序列转为01序列,当一行从下面一行转移过来的时候,如果下面三个数0多,这个位置就是0;如果就1多,这个地方就是1。然后因为一定是奇数个,所以分两种情况:
第一种:当前序列是一个01相间的序列,拿n=3举例,下面的序列会是这样(中间一列用彩色笔标出):观察发现中间是01序列,这样我们分奇数行和偶数行讨论一下解决。
第二种情况:查找距离中间最近的连续的两个0或是两个1,感觉嘴上说好像不是很好理解。
蓝色是中间的点,红色实线是离得最近的,虚线是连续两个但不是最近的。
(图片全部手画...貌似还没有找到画图的正确姿势....)
在这种情况下可以找规律,发现离得最近的连续0或1就是塔尖的数(0或1),至于证明...(我并不会证),大概是连续两个0或1递推上去就是确定,那么离中间最近的就是,最后能够一路推到塔尖的数(0或1)。
之后有了这样的结(gui)论(lv),显然满足二分性质,那么直接二分答案就好了,每次验证复杂度O(n),总时间复杂度O(nlogn)。
下面是代码(很乱...):
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<iostream> 5 #include<cmath> 6 using namespace std; 7 int in[200010],n,m,inf=1061109567; 8 bool f[200010]; 9 bool check(){ 10 int zuo=-1,you=-1,dzuo=inf,dyou=inf,i; 11 for(i=n;i>=2;i--){ 12 if(f[i]==f[i-1]){ 13 zuo=f[i],dzuo=n-i; 14 break; 15 } 16 } 17 for(i=n;i<m;i++){ 18 if(f[i]==f[i+1]){ 19 you=f[i],dyou=i-n; 20 break; 21 } 22 } 23 if(zuo!=you){ 24 if(zuo==-1) return you; 25 if(you==-1) return zuo; 26 if(dzuo<dyou) return zuo; 27 return you; 28 } 29 if(zuo==-1&&you==-1){ 30 if(n%2==1) return f[n]; 31 return f[n]^1; 32 } 33 return zuo; 34 } 35 int main() 36 { 37 int l,r,mid,i; 38 scanf("%d",&n);m=n*2-1; 39 for(i=1;i<=m;i++) scanf("%d",&in[i]); 40 l=1,r=m; 41 while(l<r){ 42 mid=(l+r)/2; 43 for(i=1;i<=m;i++){ 44 if(in[i]<mid) f[i]=0; 45 else f[i]=1; 46 } 47 if(check()==0) r=mid; 48 else l=mid+1; 49 } 50 printf("%d",l-1); 51 }
T2:
小Y和小R在探险的途中迷失在了一座森林里,走着走着,他们突然发现面前出现了一座宏伟的宫殿,在好奇心的驱使下,他们从宫殿的正门小心翼翼的走了进去...大厅的地上是一个宝石迷阵,一些宝石散布在N×MN×M的网格中。宝石分为两种,具体来说,如果第ii行jj列上存在一个宝石,且i+ji+j为偶数,那么这就是一块水晶,否则就是一块能量石,一块能量石可以向一块水晶输送能量。对于一块水晶来说,如果与它相邻的成直角的两块能量石同时向它输送能量,那么水晶便会发出彩光。破解谜题的关键就是让发光水晶的数量最大,小Y和小R陷入了沉思之中...
这题显然网络流(并不会写暴力怎么办...),行列之和为偶数的是水晶,奇数是能量石,因为每个水晶得到的能量是只能从成直角的能量石获取,那么显然每个水晶就是从一个奇数行的石头和一个偶数行的石头获取能量,这样想的话建图就很显然了,对奇数行和偶数行染色。建立源点汇点,从源点向每个在奇数行的能量石连一条容量为1的边,每个在偶数行的能量石向汇点连一条容量为1的边,保证每块能量石只能给一块水晶供能。之后每个奇数行的能量石向相邻的水晶连一条容量为1的边,每个水晶向相邻的偶数行能量石连一条容量为1的边。但是这样并不能保证每个水晶对答案只产生1的贡献,所以拆点,将每个水晶看成一个入点和一个出点,入点向出点连一条容量为1的边,保证每个点的贡献最多是1。建完图直接跑最大流就可以了...Dinic的复杂度...玄学?
直接贴代码(好长啊...):
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<iostream> 5 #include<cmath> 6 #include<queue> 7 using namespace std; 8 int head[10010],nxt[500010],whr[500010],val[500010],cnt; 9 int num[55][55],map[55][55],nn,ceng[10001],inf=1061109567; 10 char in[100]; 11 queue<int>q; 12 void add(int a,int b,int v){ 13 nxt[cnt]=head[a]; 14 whr[cnt]=b; 15 val[cnt]=v; 16 head[a]=cnt++; 17 nxt[cnt]=head[b]; 18 whr[cnt]=a; 19 val[cnt]=0; 20 head[b]=cnt++; 21 } 22 int dfs(int pos,int liu){ 23 if(pos==nn) return liu; 24 int ret=0,tmp; 25 for(int i=head[pos];i;i=nxt[i]){ 26 int t=whr[i]; 27 if(ceng[t]==ceng[pos]+1&&val[i]>0){ 28 tmp=dfs(t,min(liu,val[i])); 29 if(tmp){ 30 ret+=tmp; 31 liu-=tmp; 32 val[i]-=tmp; 33 val[i^1]+=tmp; 34 } 35 else ceng[t]=-1; 36 if(liu==0) break; 37 } 38 } 39 return ret; 40 } 41 int main() 42 { 43 int n,m,i,j,ans=0,c,t; 44 scanf("%d%d",&n,&m); 45 for(i=1;i<=n;i++){ 46 for(j=1;j<=m;j++){ 47 num[i][j]=++cnt; 48 } 49 }nn=cnt; 50 for(i=1;i<=n;i++){ 51 scanf("%s",in+1); 52 for(j=1;j<=m;j++){ 53 if(in[j]=='.') map[i][j]=1; 54 else map[i][j]=0; 55 } 56 }cnt=2; 57 for(i=1;i<=n;i++){ 58 for(j=1;j<=m;j++){ 59 if(map[i][j]==0) continue; 60 if((i+j)%2==0){ 61 add(num[i][j],nn+num[i][j],1); 62 if(i%2==1){ 63 if(i+1<=n&&map[i+1][j]==1) add(nn+num[i][j],num[i+1][j],1); 64 if(i-1>=1&&map[i-1][j]==1) add(nn+num[i][j],num[i-1][j],1); 65 if(j+1<=m&&map[i][j+1]==1) add(num[i][j+1],num[i][j],1); 66 if(j-1>=1&&map[i][j-1]==1) add(num[i][j-1],num[i][j],1); 67 } 68 else{ 69 if(i+1<=n&&map[i+1][j]==1) add(num[i+1][j],num[i][j],1); 70 if(i-1>=1&&map[i-1][j]==1) add(num[i-1][j],num[i][j],1); 71 if(j+1<=m&&map[i][j+1]==1) add(nn+num[i][j],num[i][j+1],1); 72 if(j-1>=1&&map[i][j-1]==1) add(nn+num[i][j],num[i][j-1],1); 73 } 74 } 75 else{ 76 if(i%2==0) add(num[i][j],nn+nn+1,1); 77 else add(0,num[i][j],1); 78 } 79 } 80 } 81 nn=nn+nn+1; 82 do{ 83 memset(ceng,0,sizeof(ceng)); 84 ceng[0]=1; 85 q.push(0); 86 while(!q.empty()){ 87 t=q.front(); 88 q.pop(); 89 for(i=head[t];i;i=nxt[i]){ 90 if(val[i]>0&&ceng[whr[i]]==0){ 91 ceng[whr[i]]=ceng[t]+1; 92 q.push(whr[i]); 93 } 94 } 95 } 96 if(ceng[nn]==0) break; 97 c=dfs(0,inf); 98 ans+=c; 99 }while(c); 100 printf("%d\n",ans); 101 }
T3:
SiriusRen喜欢玩一种卡牌游戏。每张牌有三种属性:攻击力、防御和速度。所有卡牌的属性值都是正整数。任何一张牌的最大可能的攻击力为x,最大可能的防御为y,最大可能的速度为z。SiriusRen手里现在有n张牌。每张卡牌的攻击力为Ai,防御为Bi,速度为Ci。定义一张卡牌能击败另一张牌当且仅当它至少有两个属性值大于另一张卡牌对应的属性值。现在SiriusRen想知道有多少不同种类的牌能击败他手里的所有牌。如果两张牌的三个属性值有至少一个不一样,两张牌就会被认为是不同的。
这道题考试的时候题面和样例出了锅(当然很快就被修正了)....然而一直没有很好的理解题意...我的理解力啊...大概题意:给定n个三元组(x,y,z)和范围xx,yy,zz,要求你找出有几个三元组满足(a,b,c) 0<a,b,c,a<=xx,b<=yy,c<=zz并且对于任意的一个三元组,你找出的三元组要至少有两个元素严格大于给定元素(我好像依旧没有说清楚)。暴力+胡乱骗分竟然有36...本来期望8分的...
部份分做法不说了...满分做法:以任意一个元素为关键字排序,接着枚举这一维度。假如说按照z排序,就枚举i从1到zz。对于这一维大于等于i的,那么需要其他两维都严格大于;对于这一维严格小于i的,那么其他两维只需要有一个严格大于就可以了。
一开始我是这么想的,按照z从小到大排序,维护x,y的后缀最大值,同时在枚举的时候动态在线段树中加点,大概就是这个意思:
图注:按照蓝色框起来的排序,红色的是xx=3,yy=3的限制,当前假设枚举的i=3,绿色是z<i的(x,y)限制,可取的是绿色阴影部分。黄色是维护的后缀最大值,枚举的i对答案的贡献就是绿色阴影和黄色阴影的交集。
一种不可做的感觉...但是有一个性质,绿色的轮廓线只会向右上走,黄色的轮廓线只会向左下走...所以就可做啦,但是这样并不好写,所以我并没有这么写。
我的另一种做法:按照z从大到小排序,一开始就把所有点的(x,y)限制(并集,就像上面绿色线一样)都加进去,然后i从zz到1倒序循环,枚举过程中每当一个点的z>=i,就把这个点限制(交集,像黄色线那样),永久加入线段树,每次统计答案就是xx*yy-覆盖面积,大概就像下面这样(点的坐标参照上图):
没有彩色阴影面积的就是答案,蓝色阴影是所有点的(x,y)取交集,黄色阴影是后面加入的点(2,2)取并集(不合法部分),然后用总面积减去被覆盖面积就是当前枚举的i对答案的贡献。
下面是代码(还算能看?):
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<iostream> 5 #include<cmath> 6 using namespace std; 7 struct segtree{ 8 long long val,mx,mi,lazy; 9 }tree[4000010]; 10 int st,ed; 11 long long xx,yy,zz,val; 12 struct node{ 13 long long x,y,z; 14 friend bool operator <(const node &a,const node &b){ 15 return a.z>b.z; 16 } 17 }in[500010]; 18 void pushdown(int l,int r,int pos){ 19 if(tree[pos].lazy!=0){ 20 int mid=(l+r)/2,lson=pos*2,rson=pos*2+1; 21 tree[lson].mx=tree[lson].mi=tree[lson].lazy=tree[pos].lazy; 22 tree[rson].mx=tree[rson].mi=tree[rson].lazy=tree[pos].lazy; 23 tree[lson].val=(mid-l+1)*tree[pos].lazy; 24 tree[rson].val=(r-mid)*tree[pos].lazy; 25 tree[pos].lazy=0; 26 } 27 return; 28 } 29 void change(int l,int r,int pos){ 30 if(st<=l&&r<=ed){ 31 if(val<=tree[pos].mi) return; 32 if(val>=tree[pos].mx){ 33 tree[pos].mx=tree[pos].mi=tree[pos].lazy=val; 34 tree[pos].val=(long long)((long long)(r-l+1)*(long long)val); 35 return; 36 } 37 } 38 pushdown(l,r,pos); 39 int mid=(l+r)/2,lson=pos*2,rson=pos*2+1; 40 if(st<=mid) change(l,mid,lson); 41 if(mid<ed) change(mid+1,r,rson); 42 tree[pos].mi=min(tree[lson].mi,tree[rson].mi); 43 tree[pos].mx=max(tree[lson].mx,tree[rson].mx); 44 tree[pos].val=tree[lson].val+tree[rson].val; 45 return; 46 } 47 int main() 48 { 49 int n,i,j=1; 50 long long ans=0ll; 51 scanf("%d%lld%lld%lld",&n,&zz,&xx,&yy); 52 for(i=1;i<=n;i++) scanf("%lld%lld%lld",&in[i].z,&in[i].x,&in[i].y); 53 sort(in+1,in+n+1); 54 for(i=1;i<=n;i++){ 55 st=1,ed=in[i].x; 56 val=in[i].y; 57 change(1,xx,1); 58 } 59 for(i=zz;i>=1;i--){ 60 while(in[j].z>=i){ 61 st=1,ed=xx; 62 val=in[j].y; 63 change(1,xx,1); 64 st=1,ed=in[j].x; 65 val=yy; 66 change(1,xx,1); 67 j++; 68 } 69 ans+=(xx*yy)-tree[1].val; 70 } 71 printf("%lld",ans); 72 }
最后总结...贴一下成绩好了....