zoukankan      html  css  js  c++  java
  • NOIP2017小结

    NOIP2017小结

    Day1

    T1

    几乎就是一道小学奥数题,然而我并做不来。但是可以用一些奇奇怪怪的方法过掉。

    (60-100pts)

    这个,我局限于考虑如何(O(1))的判断一个数字(n)能否用(a,b)表示,所以就始终不能突破(O(a*b))的时间复杂度。但是,可以通过一些鬼畜的概率算法来搞过,比如说,我把从(a*b)倒着数,最后(1000000)个长度为(10000)的区间,每个区间随机选择(50)个数字,(O(1))判断一下,然后都过就扫下一个区间,如果有不能表示的,就此终止,然后再从这里开始,往回扫(1000000)个长度为(500)的区间,每个区间随机选择(50)个数字,然后(O(1))判断一下,如果都过就扫下一个区间,如果有不能表示的,就此终止(这里就有一定的可能会舍弃最优解),然后倒着往先前的起点暴力扫,找到一个就直接输出结果。
    这么做很大程度上取决于每个区间随机选择的(50)个数字是否可以很强。但是,很幸运的是,结果就是(a*b-a-b),所以如果我从(a*b)倒着扫的时候,基本上是一定可以扫到(a*b-a-b)这一个点的,那么这个搞就得到了正确结果,只是时间效率比较慢。。
    考虑一下(O(1))判断吧,其实用(exgcd)预处理稍微推一下式子,就能很容易得出做法。

    余数法

    这个方法,很奇怪,不知道我那些同学怎么想到的(其实是我太弱了
    就是,我们不妨设(a>b),那么我们把(a)的倍数去(mod) (b),就一定可以得到(b)个余数,当然,我们很容易知道(a*b=a*0(mod) (b)),然后(a*(b+1)=a*1(mod) (b)...),也就是说,我们其实只需要考虑这几个倍数:(a*0,a*1,a*2,a*3,...,a*(b-1)),那么这里讨论一个特殊的(x=a*k(0le k<b)),对于每一个大于(x)的,并且其(mod) (b)的余数等于(x) (mod) (b)的余数的数就一定可以被表示出来,对于每一个小于(x)的,并且其(mod) (b)的余数等于(x) (mod) (b)的余数的数就一定不可以被表示出来,并且最大的就是(x-b),所以在这些倍数中,(x-b)最大的就是(a*(b-1)-b=a*b-a-b)

    Exgcd法

    T2

    这就是一个大模拟码农题哈,搞个单调栈先检查一遍是否存在语法错误,至于变量重名问题,搞个(bool)数组判重就好了,然后至于计算次幂的问题,在单调栈搞的时候,可以把每个(F)对应的(E)的映射先预处理好,然后用(dfs)搞就好了,并列的取最大值。把一些输入稍微留意的处理一下就好了。

    T3

    Day2

    T1

    还是比较简单的一道题吧。弄个两点间距离公式,建图然后跑(dijsktra)或者直接用并差集搞一搞就好了。这题的话,好像会被卡(sqrt)的精度问题,所以开(long long)然后不等式两边都平方就好了。然后的话,在有些变态数据中似乎还会被卡(long long)所以搞个移项就好了。

    T2

    其实是一个很弱的状压(dp)啊,但是考场上写的时候,没想到用(dfs)的形式去写(dp),那酸爽。。最后还没写对。。

    (40pts)

    这个部分,直接搞一个最小生成树就差不多了,因为所有的边的长度都是相等的。或者也可以枚举一下起点,然后暴力跑(dijsktra)也应该差不多。但是,只拿了(30)分,莫名其妙的。

    (70pts)

    这个部分,其实也是很简单的,直接搞一个(n^n)的暴力也就差不多了。也就是枚举每个点是由哪些点转移来的。或者可以搞打通顺序的全排列,然后就是一个贪心选取模拟(prim)了呵。
    效率就是(O(n!))

    (70-100pts)

    因为全排列会超,所以我们可以搞个随机算法,也就是弄(10^6-10^7)个随机排列,然后贪心搞一搞,因为(8!<<10^6),所以(70)的基础分还是有的,剩下的(30)分就看人品了。

    (100pts)

    网上好像有各种诡异的做法,不过有一个做法最直接最简洁,效率也是很好的,大概是(O(2^n*n^3)),其实我们直接写(dp)写不出来,写出后效性的原因就是我们无法确定(dp)的顺序,那么这就有很直接的套路:在(dfs)上写(dp)。这里用不上记忆化,所以也就不是记忆化搜索了。对于一个状态,我们通过(dfs)同时计算距离和(dp)值,然后,就很好写,然后,就没啦Σ(⊙▽⊙"a。
    好像还有人模拟退火A掉了(什么乱七八糟的东西

    参考程序

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #define rint register int
    using namespace std;
    const int N=15;
    int d[N][N],dis[N],dp[1<<N];
    int ans,n,m,oo;
    inline bool in(rint k,rint S){
    	return S&(1<<(k-1));
    }
    inline void dfs(int S){
    	for (rint i=1;i<=n;++i)
    		if (!in(i,S)){
    			for (rint j=1;j<=n;++j)
    				if (in(j,S) && d[j][i]!=oo){
    					rint tt=dis[j]+1;
    					if (dp[S|1<<(i-1)]>dp[S]+tt*d[j][i]){
    						rint t1=dis[i];
    						dis[i]=tt;
    						dp[S|1<<(i-1)]=dp[S]+tt*d[j][i];
    						dfs(S|1<<(i-1));
    						dis[i]=t1;
    					}
    				}
    		}
    }
    int main(){
    	freopen("treasure.in","r",stdin);
    	freopen("treasure.out","w",stdout);
    	scanf("%d%d",&n,&m);
    	memset(d,0x3f,sizeof(d));
    	oo=d[0][0];
    	for (rint i=1;i<=m;++i){
    		rint u,v,w;
    		scanf("%d%d%d",&u,&v,&w);
    		d[u][v]=min(d[u][v],w);
    		d[v][u]=d[u][v];
    	}
    	rint ans=oo;
    	for (rint i=1;i<=n;++i){
    		memset(dp,0x3f,sizeof(dp));
    		memset(dis,0x3f,sizeof(dis));
    		dis[i]=0;dp[1<<(i-1)]=0;
    		dfs(1<<(i-1));
    		ans=min(ans,dp[(1<<n)-1]);
    	}
    	printf("%d",ans);
    	return 0;
    }
    

    T3

    题意十分的简洁,就是需要弄个数据结构去维护一个矩阵中,删除一个位置,然后把这一行左移一个单位,再把最后一列前移一个单位。反正,我一看就觉得是线段树,直接建(n+1)个线段树,就可以直接搞了,二看就觉得空间分分钟炸掉。于是就没啥办法了。

    (30pts)

    考虑(30pts)的数据,暴力模拟就好了。

    (50pts)

    然后还有一个(20pts)的数据,(q)比较小,似乎有一种(q^2)的做法,反正我是不会。
    但是后面还有一个(20pts)的数据,保证(n=1),那么就好办了,直接暴力维护一个线段树,感觉会被卡常,我就写了个树状数组。其实就是这么维护的,我维护了2个树状数组,然后,一个维护现在这个位置的原编号,一个维护前缀和。然后删除一个点,就是把两个树状数组都弄个后缀修改,最后再在最后一个位置加回来就好了,一个简单的差分思想。也就是说,我们把删除的那个位置直接空余出来,然后再最后的位置上再添加信息,貌似2016年的那个蚯蚓,我考场上也是用线段树这么写的,好套路的东西啊

    (60pts)

    既然可以维护一个(n=1)的特殊情况,那么(x=1)的,就弄4个树状数组就好了,分别是维护第一行的标号与前缀和,维护最后一列的标号与前缀和,但是有点麻烦,而且只有10分,考场上我又没时间写了。

    1.把第一行的这个位置删掉,输出结果,然后把最后一列的第二行加进来。
    2.把最后一列的第一行删掉,把输出的结果加到最后一列的最后一行。

    可能用树状数组写的真有点麻烦,所以可以直接建2个线段树,那就好写一点了。

    (60-100pts)

    这仅仅是个思路,尽管我在想到树状数组后马上就想到了,但是由于思路很混乱,我并没敢在考场上写。
    考虑一下我们在(60)分做法中尽管效率(O(qlogn))是可以过的,但是我们的做法扩展到(n*m)上来就被卡空间了,所以我想了一个这种的办法:暴力分块(阔怕
    我们可以对于一行,把(sqrt{n})列化作一块,然后对于最后一列,再把(sqrt{n})行化作一块,对于一个块,我们维护2个信息:块的大小和块内的前缀和。
    然后删去一个点时,我们找到这一行,然后再这一行中,用二分查找确定这个点所在的块,直接把这个点删掉,然后把最后一列的对应行的加到这一行的最后一个块中,然后把这个点加到最后一列的最后一个块中。
    等等,貌似还是会被卡空间哈。因为我们没有能避免建出一个(n*m)的矩阵。
    但是,把这个思路用到线段树中,再加一些技巧就可以A了,那就是(100)分做法了。

    (100pts)

    基本原理

    首先有线段树做法:
    线段树的问题,常常都很容易被卡空间,然后就要弄一个叫动态开点的思想,就是避免一开始把所有的数据全部都建到线段树中,而是对于每次操作,都单独进行一个点修改,同时据此建一个大小为(logn)的线段树,这好像也就是主席树的基本思想哈,严格来说应该叫可持久化啊,但是这也就有麻烦了,因为如果我们在这道题中不一开始就把线段树建好,我们就存在2个麻烦:

    1.我们没有办法确定一个点在某一行中线段树中的具体位置,因为我们没有一开始就建好线段树,所以我们没有办法直接维护编号。
    2.我们没有办法确定一个点的数值,因为我们没有一开始就建好线段树,所以我们没有办法维护一开始的数值(好熟悉的一句话),那么经过操作后就更没有办法维护了。

    那么我们就单独考虑这两个麻烦。

    我们沿用分块中维护块的大小的思想,我们在这个动态的线段树中维护一个(size)域,用来表示这个区间中存在的点的个数然后二分查找一下,确定询问点的位置。然后考虑一下如何在动态开点中维护一个(size)域。其实仔细想一下也是很简单的,如果我们这个区间之前一直没有访问过,说明这个区间内的点的个数是没有受到前面操作的影响的,那么点的个数就是区间长度了,如果这个区间是访问过得,那么我们在做删除操作的时候,(size-1),在做添加操作时,(size+1)不就好了?需要注意一个细节,就是我们建的线段树应该是(max(n,m)+q)大小的啊,那么我们可能存在一开始就没有访问的区间,其右端点是大于(max(n,m)),那么在计算初始(size)域的时候,就不是区间长度了,而应该这么判断:如果其左端点小于(max(n,m))那么初始的(size)域就是(m-l),如果其左端点大于(max(n,m))那么初始的(size)域就是(0),这么做可能就会存在一个问题,也就是说,如果我们在(n+1)的位置添加了一个点,那么(n+2)(size)域在初始化的时候是不是就会出现问题?庆幸的是,这是不会出现的。因为如果(n+1)(n+2)是一个区间内的,那么这个区间就是访问过得了,那么(size)直接加加减减就行了,如果不是一个区间内的,那么(n+2)显然是(0)啊,因为我们维护的是区间内的总和,而不是整个的前缀和。因为如果维护整个的前缀和,我们在修改时,就避免不了(O(n))的修改。这个麻烦就差不多了。

    然后我们考虑一下具体数值的转移,在有上面的做法基础上,我们只需要在size域初始化的同时,直接搞一个赋为(x-1)*m+y就好了啊,然后单独弄个modify过程就好了,似乎就很简单了哈。

    Tips

    做法可能有点绕啊,所以考场上几乎想不出来呵,即使想出来了,也不一定码的出来呵。所以要想理好思绪再写。

    Some expand

    然后,好像有一个更好写,更高大上的做法(但是我太弱了,并不会写)——平衡树做法:
    搞一个非旋treap(好像也是可持久化数据结构啊)或者就是splay去做类似线段树的操作,那样可以节省一点空间,但是常数就很玄学了,尤其是对于这类卡常变态级的题目,Luogu的机子可以过,但是觉着CCF的老爷就很蛋疼了哈。

    参考程序:

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #define rint register int
    using namespace std;
    typedef long long ll;
    const int M=1e7+3;
    const int N=3e5+5;
    struct pdt{
    	int L,R,sum;
    	ll val;
    }T[M];
    int n,m,q,totn,sz,flag;
    int root[N],tot[N];
    inline int read(){
    	rint x=0,f=1;char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while('0'<=ch&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    	return x*f;
    }
    inline int getSum(int l,int r){
    	if (flag>n){
    		if (r<=n)return r-l+1;
    		if (l<=n)return n-l+1;
    		return 0;
    	}
    	if (r<m)return r-l+1;
    	if (l<m)return m-l;
    	return 0;
    }
    inline ll query(int&now,int k,int l=1,int r=totn){
    	if (!now){
    		now=++sz;
    		T[now].sum=getSum(l,r);
    		if (l==r){
    			if (flag>n) T[now].val=(ll)l*m;
    			else T[now].val=(ll)(flag-1)*m+l;
    		}
    	}
    	--T[now].sum;
    	if (l==r)return T[now].val;
    	rint mid=(l+r)>>1,tt=T[now].L?T[T[now].L].sum:(mid-l+1);
    	if (k<=tt)
    		return query(T[now].L,k,l,mid);
    	else
    		return query(T[now].R,k-tt,mid+1,r);
    }
    inline void modify(int&now,int k,ll v,int l=1,int r=totn){
    	if (!now){
    		now=++sz;
    		T[now].sum=getSum(l,r);
    		if (l==r)T[now].val=v;
    	}
    	++T[now].sum;
    	if (l==r) return;
    	rint mid=(l+r)>>1;
    	if (k<=mid) modify(T[now].L,k,v,l,mid);
    		else modify(T[now].R,k,v,mid+1,r);
    }
    int main(){
    	freopen("phalanx.in","r",stdin);
    	freopen("phalanx.out","w",stdout);
    	n=read(),m=read(),q=read();
    	totn=max(n,m)+q;
    	for (rint i=1;i<=q;++i){
    		ll ans;
    		rint x=read(),y=read();
    		if (y==m) flag=n+1,ans=query(root[n+1],x);
    			else flag=x,ans=query(root[x],y);
    		printf("%lld
    ",ans);
    		flag=n+1,modify(root[n+1],n+(++tot[n+1]),ans);
    		if (y!=m){
    			flag=n+1,ans=query(root[n+1],x);
    			flag=x,modify(root[x],m-1+(++tot[x]),ans);
    		}
    	}
    	return 0;
    }
    
    致自己:只有十分努力,才能毫不费力。
  • 相关阅读:
    juc原子类之五:AtomicLongFieldUpdater原子类
    DICOM:C-GET与C-MOVE对照剖析
    android createbitmap函数内存溢出,求解怎样进行处理out of memory溢出问题
    TRIZ系列-创新原理-32-改变颜色原理
    FP-Growth算法之频繁项集的挖掘(python)
    个人年终总结
    J2EE之ANT
    log4net 使用与配置 每天一份log文件
    Android 完美实现图片圆角和圆形(对实现进行分析)
    Unity3d修炼之路:载入一个预制体,然后为该对象加入组件,然后查找对象,得到组件。
  • 原文地址:https://www.cnblogs.com/camysj/p/7894832.html
Copyright © 2011-2022 走看看