$NOIp$普及组做题记录

\([NOIp2014]\) 螺旋矩阵

\(Sol\)

直接模拟,一次走一整行或者一整列.复杂度\(O(n)\).

\(Code\)

#include<bits/stdc++.h>
#define il inline
#define Ri register int
#define go(i,a,b) for(Ri i=a;i<=b;++i)
#define yes(i,a,b) for(Ri i=a;i>=b;--i)
#define e(i,u) for(Ri i=b[u];i;i=a[i].nt)
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define db double
#define inf 2147483647
using namespace std;
il int read()
{
    Ri x=0,y=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    return x*y;
}
int n,s,x,z,y,nx,ny,tx,ty,nd,ex,ey,nw;
il bool ck()
{
    Ri x1=nx,x2=tx,y1=ny,y2=ty;
    if(x1>x2)swap(x1,x2);if(y1>y2)swap(y1,y2);
    return (ex>=x1 && ex<=x2 && ey>=y1 && ey<=y2);
}
il int calc(){return abs(nx-ex)+abs(ny-ey);}
int main()
{
    n=read(),ex=read(),ey=read();
    if(ex==1 && ey==1){printf("1\n");return 0;}
    nx=ny=nd=1;nw=1;
    while(nd<=n*n)
    {
		if(nw==1){tx=nx,ty=n-y;++s;}
		if(nw==2){tx=n-x,ty=ny;++y;}
		if(nw==3){tx=nx,ty=z+1;++x;}
		if(nw==4){tx=s+1,ty=ny;++z;}
		if(ck()){printf("%d\n",nd+calc());break;}
		nd+=abs(nx-tx)+abs(ny-ty);nx=tx,ny=ty;++nw;if(nw>4)nw=1;
    }
    return 0;
}
	

\([NOIp2014]\)子矩阵

\(Sol\)

朴素地搜索可以获得\(80pts\).想想搜索的优化其实并不多(其实是我会的很少).所以这题可以直接记忆化搜索.具体来说,先把要选的行搜出来,然后搜要选的列的时候记忆化就行.

\(upd:\)似乎搜行的时候也可以记忆化.不过搜列记忆化足以通过此题.

\(Code\)

#include<bits/stdc++.h>
#define il inline
#define Ri register int
#define go(i,a,b) for(Ri i=a;i<=b;++i)
#define yes(i,a,b) for(Ri i=a;i>=b;--i)
#define e(i,u) for(Ri i=b[u];i;i=a[i].nt)
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define db double
#define inf 2100000000
using namespace std;
il int read()
{
    Ri x=0,y=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    return x*y;
}
const int N=20;
int n,m,r,c,a[N][N],b[N],d[N],as=inf,ct,f[20][20];
il void init(){mem(f,-1);}
il int dfs2(Ri nw,Ri lst,Ri sum)
{
    if(nw>r)return 0;
    Ri ret=inf;
    if(f[nw][lst]!=-1)return f[nw][lst];
    go(i,lst+1,n-r+nw)
    {
		d[nw]=i;Ri tmp=0;bool fl=1;
		go(j,2,c){tmp+=abs(a[i][b[j]]-a[i][b[j-1]]);if(tmp>=as){fl=0;break;}}
		if(!fl)continue;
		if(nw!=1)go(j,1,c){tmp+=abs(a[i][b[j]]-a[d[nw-1]][b[j]]);if(tmp>=as){fl=0;break;}}
		if(!fl)continue;
		ret=min(ret,dfs2(nw+1,i,tmp)+tmp);
    }
    return f[nw][lst]=ret;
}
il void dfs1(Ri nw,Ri lst)
{
    if(nw>c){init();as=min(dfs2(1,0,0),as);return;}
    go(i,lst+1,m-c+nw)
    	b[nw]=i;dfs1(nw+1,i);
}
int main()
{
    n=read(),m=read(),r=read(),c=read();
    go(i,1,n)go(j,1,m)a[i][j]=read();
    dfs1(1,0);
    printf("%d\n",as);
    return 0;	
}

\([NOIp2015]\) 推销员

\(Sol\)

一个比较直接的想法是对于每个询问,枚举走得最远的位置,然后其他的直接选(这里可以用线段树维护一下)....这样做的复杂度大约是\(O(n^2logn)\),获得\(60pts\)问题不大.

这样考虑:先直接选择\(A_i\)\(X\)大的.然后尝试舍去第\(X\)大的\(A_i\)来选择一个更远的.设选\(A_i\)\(X\)大的贡献是\(\sum A+D*2\),现在要舍弃\(x\)来换取\(y\),那么这样做可以使得贡献增加\((D_y-D)*2-A_x+A_y\).选取一个贡献最大的即可.实际上只要选取\(D_y*2+A_y\)最大的即可,这样可以直接用一个数组维护.

\(Code\)

#include<bits/stdc++.h>
#define il inline
#define Ri register int
#define go(i,a,b) for(Ri i=a;i<=b;++i)
#define yes(i,a,b) for(Ri i=a;i>=b;--i)
#define e(i,u) for(Ri i=b[u];i;i=a[i].nt)
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define db double
#define inf 2147483647
using namespace std;
il int read()
{
    Ri x=0,y=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    return x*y;
}
const int N=100010;
int n,ct,sa[N],maxd[N],maxf[N];
struct nd{int a,d;}t[N];
il bool cmp1(nd x,nd y){return x.d<y.d;}
il bool cmp(nd x,nd y){return x.a>y.a;}
int main()
{
    n=read();
    go(i,1,n)t[i].d=read();
    go(i,1,n)t[i].a=read();
    sort(t+1,t+n+1,cmp);
    go(i,1,n)if(t[i].a==0){n=i-1;break;}
    go(i,1,n)sa[i]=sa[i-1]+t[i].a,maxd[i]=max(maxd[i-1],t[i].d);
    yes(i,n,1)maxf[i]=max(maxf[i+1],t[i].d*2+t[i].a);
    go(i,1,n)printf("%lld\n",max(sa[i]+maxd[i]*2,sa[i-1]+maxf[i+1]));
    return 0;
}

\([NOIp2015]\)求和

\(Sol\)

简化下题意,纸带的分数是:\(\sum_{i<j,(j-i)\&1=0}(i+j)(num_i+num_j)\).

直接枚举相同颜色的\(i,j\),\(check\ (j-i)\)是否为偶数累加答案,可以获得\(80pts\).如果可以省去\(check()\)这一步就好了.其实可以按照奇偶分组,同一组内,只要颜色相同就可以对答案产生贡献.

假设\([l,r]\)区间都是一种颜色,答案累加:

\(\sum_{i=l}^{r}\sum _{j=i+1}^r(i+j)(num_i+num_j)\)

\(=\sum_{i=l}^r i*num_i*(r-l-1)+\sum_{i=l}^{r}\sum _{j=i}^r i*num_j\)

\(=\sum_{i=l}^r i*num_i*(r-l-1)+\sum_{i=l}^{r}i *\sum_{i=l}^r num_i\)

注意特判\(l=r\)的情况.

\(Code\)

#include<bits/stdc++.h>
#define il inline
#define Ri register int
#define go(i,a,b) for(Ri i=a;i<=b;++i)
#define ll long long	if(ct>1)as+=1LL*s3*(ct-2)%mod+1LL*s1*s2%mod;as%=mod;s1=s2=s3=ct=0;

using namespace std;
il int read()
{
    Ri x=0,y=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    return x*y;
}
const int N=100010,mod=10007;
int n,m,as,n1,n2;
struct nd{int num,col,pos;}a[N],b[N];
il bool cmp(nd x,nd y){return x.col<y.col;}
int main()
{
    freopen("1.in","r",stdin);
    n=read(),m=read();
    go(i,1,n)if(i&1)a[++n1].num=read(),a[n1].pos=i;else b[++n2].num=read(),b[n2].pos=i;
    n1=n2=0;
    go(i,1,n)if(i&1)a[++n1].col=read();else b[++n2].col=read();
    sort(a+1,a+n1+1,cmp);sort(b+1,b+n2+1,cmp);
    Ri s1=0,s2=0,s3=0,ct=0;
    go(i,1,n1)
    {
	s1+=a[i].num,s2+=a[i].pos,s3+=1LL*a[i].num*a[i].pos%mod,++ct;
	s1%=mod,s2%=mod,s3%=mod;
	if(a[i].col==a[i+1].col)continue;
	if(ct>1)as+=1LL*s3*(ct-2)%mod+1LL*s1*s2%mod;as%=mod;s1=s2=s3=ct=0;
    }
    s1=s2=s3=ct=0;
    go(i,1,n2)
    {
	s1+=b[i].num,s2+=b[i].pos,s3+=1LL*b[i].num*b[i].pos%mod,++ct;
	s1%=mod,s2%=mod,s3%=mod;
	if(b[i].col==b[i+1].col)continue;
	if(ct>1)as+=1LL*s3*(ct-2)%mod+1LL*s1*s2%mod;as%=mod;s1=s2=s3=ct=0;
    }
    printf("%d\n",as);
    return 0;
}

\([NOIp2016]\)海港

\(Sol\)

开一个队列存以当前时间为结束的\(24\)小时内到达的游客(包括时间和国际).一个数组\(s[i]\)表示当前队列中国籍为\(i\)的游客有多少.一个变量\(nw\)记录当前队列中有多少不同国籍的游客.因为保证输入时间是递增的可以在线做,没必要离线做.每次来了一条新船,就把队首时间不合法的游客移除,顺便维护下\(s[i]\),如果有\(s[i]\)因此变为\(0\)了,那么就\(--nw\).然后在队尾加入这条船的游客,维护$s[i] \(,若有\)s[i]$因此变为\(1\),那么\(++nw\).最后直接输出\(nw\),进入下一次循环即可.

\(Code\)

#include<bits/stdc++.h>
#define il inline
#define Ri register int
#define go(i,a,b) for(Ri i=a;i<=b;++i)
using namespace std;
il int read()
{
    Ri x=0,y=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    return x*y;
}
const int N=100010;
int n,ct,q[N*3],l=1,r,s[N*3],nw;
struct nd{int t,k;}a[N*3];
int main()
{
    n=read();
    go(i,1,n)
    {
	Ri t=read(),k=read();
	go(i,1,k)
	{
	    Ri kk=read();
	    a[++ct]=(nd){t,kk};q[++r]=ct;
	    ++s[kk];if(s[kk]==1)++nw;
	}
	while(a[q[l]].t<=t-86400){if(!(--s[a[q[l]].k]))--nw;++l;}
	printf("%d\n",nw);
    }
    return 0;
}

\([NOIp2017]\) 棋盘 ​

$Sol $

看起来十分像一道搜索题.需要记录地状态:当前在哪一个格子,已经花费多少金币,当前可不可以使用魔法.还要标记走过的格子.这样没有任何剪枝地搜就可以获得\(55pts\)的好成绩.

加一个最优性剪枝就可以多获得\(10pts\).

记一个\(rem[i][j]\)表示从\((1,1)\)\((i,j)\)这个点花的最小代价,如果再搜到这里的时候代价大于它,那么就\(return\).这样做不仅可以剪枝,而且还可以少记一个\(vis\),于是少维护\(vis\)又可以加快速度.

\(upd\):这样做当且仅当再走到这里的时候代价一定大于原来的情况,可是这题不一定叭?假如有一个同颜色的环,这样还是会死循环下去.

\(再upd\),其实这里是可以按上面的做法的,只是一定是代价大于等于的时候\(return\),而且这样的话一定不可以记\(vis\).

另外一个要注意的点(特别是如果要维护$vis \(的话):不优的情况与其\)dfs\(下去再判掉不如**在\)dfs$之前就判掉.**

\(Code\)

#define il inline
#define Ri register int
#define go(i,a,b) for(Ri i=a;i<=b;++i)
#define yes(i,a,b) for(Ri i=a;i>=b;--i)
#define e(i,u) for(Ri i=b[u];i;i=a[i].nt)
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define db double
#define inf 2147483647
using namespace std;
il int read()
{
    Ri x=0,y=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    return x*y;
}
const int N=110;
int m,n,col[N][N],dx[4]={1,0,-1,0},dy[4]={0,1,0,-1},as=inf,rem[N][N];
il void dfs(Ri nx,Ri ny,Ri color,Ri sum,bool mag)
{
    if(sum>=as)return;
    if(nx==ny && nx==m){as=min(as,sum);return;}
    rem[nx][ny]=sum;
    go(i,0,3)
    {
		Ri tx=nx+dx[i],ty=ny+dy[i];
			if(tx<1 || tx>m || ty<1 || ty>m)continue;
	if(col[tx][ty])
	{
		    if(col[tx][ty]==color && (rem[tx][ty]==-1 || sum<rem[tx][ty]))
		    {dfs(tx,ty,col[tx][ty],sum,1);}
		    else if(rem[tx][ty]==-1 || sum+1<rem[tx][ty])
		    {dfs(tx,ty,col[tx][ty],sum+1,1);}
	}
	else if(mag && (rem[tx][ty]==-1 || sum<rem[tx][ty]))
	{dfs(tx,ty,color,sum+2,0);}
    }
}
int main()
{
    m=read(),n=read();mem(rem,-1);
    go(i,1,n){Ri x=read(),y=read(),c=read();col[x][y]=c+1;}
    dfs(1,1,col[1][1],0,1);
    if(as==inf)as=-1;
    printf("%d\n",as);
    return 0;
}

\([NOIp2017]\) 跳房子 ​

\(Sol\)

首先判断有无解就是把所有的分数加起来与\(k\)比较.

部分分是可以搜索的,就搜索出依次跳到哪些格子可以至少获得\(k\)分,然后统计一下答案就可以了.这样就获得了\(20pts\).

觉得可以\(dp.jpg\).

\(f[i][j]\)表示前\(i\)个格子,且选了第\(i\)个格子,花费\(j\)改进,可以获得的最大价值.

\(f[i][j]=max (f[k][g])+a[i],k<i\ 且\ abs(x[i]-x[k]-d)<=j且g<=j\).

然后\(max(f[k][g])\)这里可以用单调队列优化一下?

然而这样并不足以\(AC\),时间空间上都有问题.如果能在\(f\)上省去一维就好了.真的不会,于是去看了题解.

理解题解之后的:其实上述\(dp\)的第二维相当于在枚举最小花费,又应为最小花费显然可以二分.所以简单来说:首先二分最小花费,然后对这个最小花费\(dp\),最后统计答案.\(over\).

\(QwQ\)

写代码自闭了,真的要仔仔细细认认真真地思考一遍再写代码吖.几乎每次对代码掉以轻心,觉得直接写就好了的时候就会卡很久,到处出错,实际花的时间多得多.别忘了你是个菜鸡啊!写代码要沉着,冷静,放慢速度.

\(Code\)

#include<bits/stdc++.h>
#define il inline
#define Ri register int
#define go(i,a,b) for(Ri i=a;i<=b;++i)
#define yes(i,a,b) for(Ri i=a;i>=b;--i)
#define e(i,u) for(Ri i=b[u];i;i=a[i].nt)
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define inf 2147483647
using namespace std;
il int read()
{
    Ri x=0,y=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    return x*y;
}
const int N=500010;
int n,d,k,x[N],s[N],f[N],as=inf,L,R,mid,q[N];
il bool Ck()
{
    ll sum=0;Ri las=0;
    yes(i,n,1)if(s[i]>0){sum+=s[i];R=max(R,abs(x[i]-x[las]-d));las=i;};
    return (sum>=k);
}
il bool ck(Ri g)
{
    go(i,1,n)f[i]=-inf;f[0]=0;
    Ri l=1,r=0,las=0;
    go(i,1,n)
    {
		while(l<=r && x[q[l]]<x[i]-d-g)l++;
		while(las<=n && x[las]<x[i]-d-g)las++;
		while(las<i && x[las]<=x[i]-d+g )
		{
	  		  if(f[las]==-inf){++las;continue;}
	  		  while(l<=r && f[q[r]]<=f[las])r--;
	   		 q[++r]=las;++las;
		}
		if(l<=r)f[i]=f[q[l]]+s[i];
		if(f[i]>=k)return 1;
    }
    return 0;
}
int main()
{
    n=read(),d=read(),k=read();
    go(i,1,n)x[i]=read(),s[i]=read();
    if(!Ck()){printf("-1\n");return 0;}
    while(L<=R)
    {
		mid=(L+R)>>1;
		if(ck(mid))as=mid,R=mid-1;
    }
    printf("%d\n",as);
    return 0;
}

\([NOIp2018]\)对称二叉树

\(Sol\)

最初以为只要中序遍历是回文串就是对称二叉树,于是写了下只有\(64pts\).其实上面那个结论错得挺显然的.应该是这样的:首先是根的左孩子和右孩子相等,然后左孩子的左孩子和右孩子的右孩子相等,左孩子的右孩子和右孩子的左孩子相等.....这个可以以每个结点作为根\(check\)一次.本来打算先写个暴力,结果就\(AC\)辣.感觉复杂度似乎是\(O(nlogn)\).

还有个\(O(n)\)\(hash\)做法,其实我感觉就是我最开始那个回文串做法的改进.可以直接\(hash\)树的形态,举个栗子:一棵三个结点的二叉树\(root,lson,rson\),那么其\(hash\)值为\(lson*bas1+rson*bas2+root*bas3\).要计算两个\(hash\)值,一个是先走左儿子的中序遍历值\(hash1\),一个是先走右儿子的\(hash2\),对于\(root\),若\(hash1[lson]=hash2[rson]\)那么就是对称二叉树.另外,记一下\(TJ\)中用的\(bas\),\(b1=999999751,b2=299999827,b3=100000007,m1=89999794200117649,m2=999999786000011449\).

\(Code\)

#include<bits/stdc++.h>
#define il inline
#define Ri register int
#define go(i,a,b) for(Ri i=a;i<=b;++i)
#define yes(i,a,b) for(Ri i=a;i>=b;--i)
#define e(i,u) for(Ri i=b[u];i;i=a[i].nt)
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define db double
#define inf 2147483647
using namespace std;
il int read()
{
    Ri x=0,y=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    return x*y;
}
const int N=1000010;
int n,l[N],r[N],w[N],ct,sz[N],as;
il void dfs(Ri nw)
{
    sz[nw]=1;
    if(l[nw]>-1)dfs(l[nw]),sz[nw]+=sz[l[nw]];
    if(r[nw]>-1)dfs(r[nw]),sz[nw]+=sz[r[nw]];
}
il int ck(Ri x,Ri y)
{
    if(x==-1 && y==-1)return 1;
    if(x==-1 || y==-1)return 0;
    if(w[x]!=w[y])return 0;
    if(ck(l[x],r[y]) && ck(r[x],l[y]))return 1;
    return 0;
}
int main()
{
    n=read();
    go(i,1,n)w[i]=read();
    go(i,1,n)l[i]=read(),r[i]=read();
    dfs(1);
    go(i,1,n)if(ck(l[i],r[i]))as=max(as,sz[i]);
    printf("%d\n",as);
    return 0;
}

\([NOIp2018]\) 摆渡车

posted @ 2019-10-13 09:13  DTTTTTTT  阅读(127)  评论(0编辑  收藏  举报