牛客网NOIP赛前集训营-提高组(第七场)Solution

先吐槽一下,这次的比赛稍微有点水了。。。\(kcz\) 神仙 \(40min\) 做完 \(3\) 道题 \(rank1\) 到最后竟然掉了 \(73\) 分。。。

Problem A. 中国式家长 2

大水题,直接按题意模拟即可,没什么好说的,全场切,\(kcz\) 神仙 \(6min\)
#include<cstdio>
#include<iostream>
using namespace std;
const int N=2e2+10;
int n,m,k,T,a[N][N],x,y,Walk,IQ;
int dx[8]={-1,-1,0,1,1,1,0,-1},dy[8]={0,1,1,1,0,-1,-1,-1};
bool b[N][N],Dig[N][N];
int main(){
    scanf("%d%d%d",&n,&m,&k);Walk=k;
    for(register int i=1;i<=n;i++)
        for(register int j=1;j<=m;j++)
            scanf("%d",&a[i][j]);
    for(register int i=1;i<=n;i++)
        for(register int j=1;j<=m;j++)
            scanf("%d",&b[i][j]);
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&x,&y);
        if(!b[x][y]||Dig[x][y]||(!a[x][y]&&Walk<10)){printf("-1 -1\n");return 0;}
        Dig[x][y]=true;
        for(register int i=0;i<=7;i++)b[x+dx[i]][y+dy[i]]=true;
        if(!a[x][y])Walk-=10,IQ+=10;
        else Walk=min(Walk+a[x][y],k);
    }
    printf("%d %d\n",Walk,IQ);return 0;
}

Problem B. 随机生成树

问题可以转化为把 \(n\) 个点按题意连接后,颜色相同且联通的一块被称为一个联通块,求最多有多少联通块
题目要求它的父亲必须是它的约数,那么我们考虑枚举每一个数的约数,显然这样做复杂度是 \(O(n\sqrt n)\) 级别的,这也是为什么本题时限竟然有 \(2s\),因为常数小一点这样的乱搞也能过。(PS:我最近都被暴力吓怕了,前两次模拟赛有三道题全部给了暴力满分,这回暴力写的好也过了,真不知道出题人是怎么想的)
我们当然不会满足于这样的乱搞算法,所以很快,我们就想到另一种更优的算法:直接对于每个数枚举它的倍数,然后对应更新答案即可。因为要让联通块尽量的多,那么每个点都要尽量往与它颜色不同的节点上连,这样贪心下去就可以保证是最优的了,复杂度大概是 \(O(n\log n)\),所以这道题数据范围扩大到 \(5000000\) 都没有问题。
这题 \(kcz\) 只用了 \(4min\) 就切掉了。。。
#include<cstdio>
#include<iostream>
using namespace std;
const int N=5e5+10;
int n,a[N],fa[N],head[N],cnt,pre[N],ans;
struct edge{int nxt,to;}ed[N];
inline void addedge(int x,int y){ed[++cnt].to=y;ed[cnt].nxt=head[x];head[x]=cnt;}
inline void DFS(int u){
    pre[u]=ans;
    for(register int i=head[u];i;i=ed[i].nxt)
        if(a[u]==a[ed[i].to])DFS(ed[i].to);
}
int main(){
    scanf("%d",&n);
    for(register int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(register int i=1;i<=n;i++){
        if(i!=1)addedge(fa[i],i);
        for(register int j=(i<<1);j<=n;j+=i)
            if(!fa[j])fa[j]=i;else if(a[i]!=a[j])fa[j]=i;
    }
    for(register int i=1;i<=n;i++)if(!pre[i])++ans,DFS(i);
    printf("%d\n",ans);return 0;
}

Problem C. 洞穴

这题思想挺不错的,当时在考场上已经想差不多了,就是设 \(dis[i][j][k]\) 表示从 \(i\)\(k\) 步是否可以到达 \(j\),但没想到倍增,硬设肯定是不行的,毕竟最多要走十亿多步,然后就陷入了图中环的深渊无法自拔,瞎转移一波成功得到了 \(5\) 分。。。接下来说说正解
还是和上面差不多,我们设 \(dis[i][j][k]\) 表示从 \(i\)\(2^j\) 步是否可以到达 \(k\),然后我们发现这东西的值只有 \(0\)\(1\),所以直接用 \(bitset\) 把最后一维压掉,这样位运算的复杂度就变成了 \(O(N/64)\) 了,可以加快速度
然后考虑转移,如果 \(i\)\(2^j\) 步可以到达 \(k\),那么显然 \(i\)\(2^{j+1}\) 步就可以达到 \(k\)\(2^j\) 步所能达到的所有节点,那么就有以下的转移方式

\[dis[i][j+1]|=dis[k][j],dis[i][j][k]==1 \]

这样我们就完成了预处理
那么我们这样倍增到底有什么好处呢?可以发现,对于每一个询问里的 \(l\),它在二进制下都由若干个 \(1\) 组成,那么我们只要找到这些 \(1\),并将当前可到达的节点倍增走出去即可。例如当前可以走到的节点状态为 \(ans\)\(bitset\) 压位),那么我们就可以枚举 \(ans\) 所有不为 \(0\) 的位置,然后用 \(dis[i][j]\) 再往外走就行了
复杂度大概为 \(O(\frac{Qlogln^2}{64}+\frac{n^3logn}{64})\),代码如下:
#include<cstdio>
#include<iostream>
#include<bitset>
using namespace std;
const int N=1e2+10;
const int LOG=31;
int n,m,U,V,l,a,b,Q;
bitset<N>dis[N][33],ans,tmp;
int main(){
	scanf("%d%d",&n,&m);
	for(register int i=1;i<=m;i++)
		scanf("%d%d",&U,&V),dis[U][0][V]=1;
	for(register int j=0;j<=LOG;j++)
		for(register int i=1;i<=n;i++)
			for(register int k=1;k<=n;k++)
				if(dis[i][j][k])dis[i][j+1]|=dis[k][j];//若数组紧贴着LOG开这里加一会超过范围!!! 
	scanf("%d",&Q);
	while(Q--){
		scanf("%d%d%d",&l,&a,&b);
		ans.reset();ans[a]=1;
		for(register int i=0;i<=LOG;i++){
			if(!(l>>i))break;
			if((l>>i)&1){
				tmp=ans;ans.reset();
				for(register int j=1;j<=n;j++)if(tmp[j])ans|=dis[j][i];
			}
		}
		puts(ans[b]? "YES":"NO");
	}
	return 0;
}
posted @ 2018-10-28 17:40  ForwardFuture  阅读(166)  评论(0编辑  收藏  举报