您的位置:首页 > 其它

NOIP2017模拟赛8

2017-06-20 14:59 176 查看

前言

为什么总结只有1和8,没有2、3、4、5、6、7呢?因为只有这两次是AK才有时间写啊!



a路径

题目很长,简单一点来说就是一个二维平面坐标系中有N个整数点,主人公Bessie,每一步可以走上下左右四个点,要求你从(0,0)出发,走过所有给定的点,且结束点是任意一个给定的点,问是否有一个方案所有步数之和的奇偶性为P。

分析

这题作为一道签到题,想一下就会发现这道题唯一的条件“步数之和”只与结束点有关,与其他都是没有关系的,所以对于每一个点,将它的坐标x和y加起来判断奇偶即可。

程序

就不贴了吧。

b冠军

有N个拳手参加比赛,

分析

这题什么乱七八糟的方法(其实大部分都是在N!的方法上改进的)我都想过,最后发现除了状压加搜索优化以外,没有什么更好的方法。

f[i][j][P]表示到了第i轮比赛,胜者是第j个拳手,P表示哪几位拳手参加了比赛。

搜索dfs(k,s,w,p)表示搜索到前k个拳手已经搜索完,一共有s个拳手被选中,其中胜利的是第w个拳手,这s个拳手是这p个。

其中第w个拳手一定要输给第j个拳手,且第j个拳手不能在这s个拳手中。

为了节省时间,一开始我还把有效的P找出来,但现在分析一想,并没有优化什么太多时间复杂度。

这题如果时间开1s确实很卡。。

#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <cmath>
#include <cstring>
#include <vector>
#include <time.h>

using namespace std;
long long f[6][20][70000];
int N,w,P,n,m,sl[20],b[20][70000],a[20][20],tep[20];
char st[20];

void dfs(int k,int s,int p,int lw) {
if ((s<<1)==(1<<N)) {
if (lw==-1) return;
f
[w][P]+=f[N-1][lw][p]*f[N-1][w][P-p]*2;
return ;
}
for (int i=k;i<=(1<<N);i++)
{
if (lw==-1&&a[w][tep[i]]) dfs(i+1,s+1,p|(1<<tep[i]),tep[i]);
if (tep[i]!=w) dfs(i+1,s+1,p|(1<<tep[i]),lw);
}
}

int main(){
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);

scanf("%d",&n);
for (int i=0;i<n;i++){
scanf("%s",&st);
for (int j=0;j<n;j++)
if (st[j]=='N') a[i][j]=0;else a[i][j]=1;
}

int l=1<<n;
for (register int i=0;i<l;i++) {
int i1=i,s=0;
while (i1>0) {++s;i1-=i1&-i1;}
if (s==2) s=1; else
if (s==4) s=2; else
if (s==8) s=3; else
if (s==16) s=4; else
s=0;
if (s!=0){
sl[s]++;b[s][sl[s]]=i;
}
}

for (int i=0;i<n;i++)
for (int j=0;j<n;j++)
if (a[i][j]) f[1][i][(1<<i)+(1<<j)]=2;

if (n==2) m=1;if (n==4) m=2;if (n==8) m=3;if (n==16) m=4;
for (int i=2;i<=m;i++)
for (register int j=1;j<=sl[i];j++) {
int cnt=0;
for (int k=0;k<n;k++)
if (b[i][j]&(1<<k)) tep[++cnt]=k;
P=b[i][j];N=i;
for (int k=1;k<=cnt;k++){
w=tep[k];
dfs(1,0,0,-1);
}
}

for (int i=0;i<n;i++)
printf("%lld\n",f[m][i][(1<<n)-1]);

return 0;
}


中间的
if (n==2) m=1;if (n==4) m=2;if (n==8) m=3;if (n==16) m=4;
为什么用log(n)/log(2)算不出来,求大神解答。

小结一波

这道题的确卡了我非常多时间,首先一开始我认为状态转移需要很多时间,粗略一点就是216,就算是运用组合数减少转移状态的时间也不会好到哪里去。我没有算C,导致我以为C816很大,不比216少多少,算出来也证明只比216少6倍,但在这种卡常数的题中,少6倍就足够了。

C指纹

给你n组四元组{a,b,c,d},其中如果每一个四元组A中的元素,有至少3个比另一个四元组B中的元素要小,则认为四元组B是“累赘”的。问有哪几组四元组是累赘的。

分析

由于第二题花了比较多的时间,所以这一题我想得并不是很深入。

首先n很大,就已经卡掉了很多譬如网络流之类的乱七八糟的算法。

对于本题而言,nn√和nlog2n的时间复杂度都是可以接受的,很容易想去数据结构和分块方面。其中分块我没想过,也不好想,所以我直接想数据结构方面了。(毕竟现在的oier数据结构都学得很好,当然蒟蒻我只会线段树)

一共有四维数据。首先肯定要先对第一维排序,这样子就可以减少一维数据的干扰。

然后问题来了,对于每一组四元组,你可以有两种方法进行处理,一种是判断这个四元组是不是“累赘”,另一种是用这个四元组来判断前面的四元组是不是“累赘”。由于第二种方法具有不确定性,相对来说第一种方法看上去简单很多。(对于暴力来说,两种方法并没有什么区别)

然后想如何不用排序的方式来判第二维的大小,然后就想到以数组下标为基础的桶排,然后用数据结构询问比第二维小的有没有就可以了。

然后考虑第三维,看着这个数据结构,我很惊奇地发现,这颗线段树里空空如也,我们就可以把第三维记进去。

既然这样,那也顺便把第四维也记进线段树里面。

总的来说,就是以第一维排序,那么先加入数据结构里面的a一定比当前a要小。然后在在线段树中1~b中找最小的c和d,最后判断即可。

这种方法还有一个缺陷,就是某一个四元组是“累赘”的,但a比较小,你没有把它视为“累赘”,所以这样做完一次以后,将c、d和a、b交换再做同样的操作即可。

程序

#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <cmath>
#include <cstring>
#include <vector>
#include <time.h>

using namespace std;

struct node{
int a,b,c,d,e;
}a[100010];
int T[2][400010],n;
bool ans[100010];

bool cmp(node A,node B) {
return A.a<B.a;
}

void update(int p,int ro,int L,int R,int x,int v) {
if (L==R&&R==x) {T[p][ro]=v; return;}
if (L>x||R<x) return;
int mid=(L+R)>>1,zuo=ro<<1,you=zuo+1;
update(p,zuo,L,mid,x,v);
update(p,you,mid+1,R,x,v);
T[p][ro]=min(T[p][zuo],T[p][you]);
}

int query(int p,int ro,int L,int R,int le,int ri) {
if (le<=L&&R<=ri) return T[p][ro];
if (R<le||L>ri) return n+1;
int mid=(L+R)>>1,zuo=ro<<1,you=zuo+1;
int A=query(p,zuo,L,mid,le,ri),
B=query(p,you,mid+1,R,le,ri);
return min(A,B);
}

void work(){
sort(a+1,a+1+n,cmp);
for (int i=0;i<=4*n;i++) T[0][i]=T[1][i]=n+1;
update(0,1,1,n,a[1].b,a[1].c);
update(1,1,1,n,a[1].b,a[1].d);
for (int i=2;i<=n;i++) {
int A=query(0,1,1,n,1,a[i].b),
B=query(1,1,1,n,1,a[i].b);
if (A<a[i].c||B<a[i].d) ans[a[i].e]=false;
update(0,1,1,n,a[i].b,a[i].c);
update(1,1,1,n,a[i].b,a[i].d);
}
}

int main(){
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);

scanf("%d",&n);
for (int i=1;i<=n;i++) ans[i]=true;
for (int i=1;i<=n;i++) {
scanf("%d%d%d%d",&a[i].a,&a[i].b,&a[i].c,&a[i].d);
a[i].e=i;
}
work();
for (int i=1;i<=n;i++)
{
swap(a[i].a,a[i].c);
swap(a[i].b,a[i].d);
}
work();int cnt=0;
for (int i=1;i<=n;i++)
if (!ans[i]) cnt++;
printf("%d\n",cnt);
for (int i=1;i<=n;i++)
if (!ans[i]) printf("%d\n",i);

return 0;
}


小结

这题我联想起gdoi2016的某一天的第二题,做暴力并用抽屉原理来优化,但好像本题不是用这个方法。



——-zero镇楼

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: